在这种特殊情况下,静态数组的速度慢700倍是否合理?

我必须承认我不太熟悉C。在尝试将Perl模块LCS::BV移植到C并调整我在一个会话中获得的代码的速度时,每秒发现5 G迭代的速度令人惊讶(相比之前的12 M / s)。

现在隔离原因期间,在Intel Core i7-4770HQ上,差异为〜50 G / s至〜70 M / s。

为确保clang不会展开基准测试的循环,我可以使用_mm_popcnt_u64(~v)然后进行反编译:

$ otool -tv lcstest > lcstest.dump.txt
$ grep popcnt lcstest.dump.txt
0000000100000e26    popcntq %rax,%rax

我不确定基准代码或方法是否有问题。请参见带注释的代码(也可以在GitHub上找到):

#include <stdio.h>
#include <limits.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <nmmintrin.h>

static const uint64_t width = 64;

int count_bits(uint64_t bits) {
  bits = bits - ((bits >> 1) & 0x5555555555555555ull);
  bits = (bits & 0x3333333333333333ull) + ((bits >> 2) & 0x3333333333333333ull);
  // (bytesof(bits) -1) * bitsofbyte = (8-1)*8 = 56 -------------------------------vv
  return ((bits + (bits >> 4) & 0x0f0f0f0f0f0f0f0full) * 0x0101010101010101ull) >> 56;
}

int llcs_asci (char * a,char * b,uint32_t alen,uint32_t blen) {
    // static uint64_t posbits[128] = { 0 }; //    73.4 (M/sec)
    // uint64_t posbits[128] = { 0 };        // 53050.4 (M/sec)
    uint64_t posbits[128];                   // 56338.0 (M/sec)

    uint64_t i;   
    for (i=0; i < 128; i++) { posbits[i] = 0; } // needed

    for (i=0; i < alen; i++) {
        posbits[(unsigned int)a[i]] |= 0x1ull << (i % width);
    }    

    uint64_t v = ~0ull;

    for (i=0; i < blen; i++) {
      uint64_t p = posbits[(unsigned int)b[i]];
      uint64_t u = v & p;
      v = (v + u) | (v - u);
    }

    return count_bits(~v);  // portable
    //return _mm_popcnt_u64(~v); 
}

int main (void) {
    clock_t tic;
    clock_t toc;
    double elapsed;
    double rate;

    uint64_t count;
    uint64_t megacount;
    uint32_t iters     = 1000000;
    uint32_t megaiters = 1;

    // m=10,n=11,llcs=7,d=4,sim=0.667
    char str1[] = "Choerephon";
    char str2[] = "Chrerrplzon";

    uint32_t len1 = strlen(str1);
    uint32_t len2 = strlen(str2);

    int length_lcs;

    /* ########## llcs_asci ########## */

    tic = clock();

    megaiters = 20;

    for (megacount = 0; megacount < megaiters; megacount++) {
      for (count = 0; count < iters; count++) {
          length_lcs = llcs_asci (str1,str2,len1,len2);
      }
    }

    toc = clock();
    elapsed = (double)(toc - tic) / (double)CLOCKS_PER_SEC;
    rate    = (double)megaiters / (double)elapsed;

    // need to use the result to avoid loop unrolling ---------------------vv
    printf("[llcs_asci] iters: %u M Elapsed: %f s Rate: %.1f (M/sec) llcs: %u\n",megaiters,elapsed,rate,length_lcs);

    /* #################### */

    return 0;

}
wc031546 回答:在这种特殊情况下,静态数组的速度慢700倍是否合理?

尝试在两种情况下查看代码的反汇编并进行比较。

非静态-https://godbolt.org/z/T2m7CQ

具有静态数组-https://godbolt.org/z/naZWEY

看看main函数!编译器了解到所有循环迭代都没有任何副作用,实际上只有最后一次迭代会影响length_lcs,因此编译器会删除以下代码:

for (megacount = 0; megacount < megaiters; megacount++) {
  for (count = 0; count < iters; count++) {
      length_lcs = llcs_asci (str1,str2,len1,len2);
  }
}

main:                                   # @main
        push    r14
        push    rbx
        push    rax
        xor     ebx,ebx
        call    clock
        mov     r14,rax
.LBB2_1:                                # =>This Loop Header: Depth=1
        mov     eax,1000000
.LBB2_2:                                #   Parent Loop BB2_1 Depth=1
        add     rax,-25
        jne     .LBB2_2
        add     rbx,1
        cmp     rbx,20
        jne     .LBB2_1
        call    clock
        sub     rax,r14
        cvtsi2sd        xmm0,rax
        divsd   xmm0,qword ptr [rip + .LCPI2_0]
        movsd   xmm1,qword ptr [rip + .LCPI2_1] # xmm1 = mem[0],zero
        divsd   xmm1,xmm0
        mov     edi,offset .L.str
        mov     esi,20
        mov     edx,7
        mov     al,2
        call    printf
        xor     eax,eax
        add     rsp,8
        pop     rbx
        pop     r14
        ret

但是,当使用static数组时,编译器仍然必须更新static数组的值,因为它必须“记住”最新值。尽管在每个条目上清除了整个数组,但是即使在执行不在{{1之外的时候,也可以检查/测试(例如,通过调试器)为posbits分配的内存(定义为静态数组) }}功能。

llcs_asci

请注意,这些优化是可用的,因为执行基准测试的功能(在这种情况下为main: # @main push r15 push r14 push rbx xor r15d,r15d call clock mov r14,rax .LBB2_1: # =>This Loop Header: Depth=1 mov ebx,1000000 .LBB2_2: # Parent Loop BB2_1 Depth=1 mov edi,offset llcs_asci.posbits mov edx,1024 xor esi,esi call memset mov qword ptr [rip + llcs_asci.posbits+536],1 mov qword ptr [rip + llcs_asci.posbits+912],16 mov qword ptr [rip + llcs_asci.posbits+808],40 mov qword ptr [rip + llcs_asci.posbits+896],64 mov qword ptr [rip + llcs_asci.posbits+832],130 movaps xmm0,xmmword ptr [rip + .LCPI2_0] # xmm0 = [512,260] movaps xmmword ptr [rip + llcs_asci.posbits+880],esi call memset movaps xmm0,260] mov qword ptr [rip + llcs_asci.posbits+536],130 movaps xmmword ptr [rip + llcs_asci.posbits+880],xmm0 add rbx,-2 jne .LBB2_2 add r15,1 cmp r15,r14 xorps xmm0,xmm0 cvtsi2sd xmm0,qword ptr [rip + .LCPI2_1] movsd xmm1,qword ptr [rip + .LCPI2_2] # xmm1 = mem[0],eax pop rbx pop r14 pop r15 ret )和正在测试的功能(main)都是在同一编译单元中,这使编译器可以内联代码并执行更积极的优化。尝试将llcs_asci函数移动到另一个文件,并在迭代之间创建依赖关系(例如,通过在结果llcs_asci之间进行按位或。然后重新运行基准测试,我相信您会或多或少地获得结果相同。

本文链接:https://www.f2er.com/3078613.html

大家都在问