What is my compiler doing? (optimizing memcpy)

吃可爱长大的小学妹 提交于 2019-12-03 12:38:45

eax+70h read is moved up because eax+70h is in a different cache line from eax, and the compiler probably wants the hardware prefetcher to get busy getting that line as soon as possible.

It does not interleave either because it wants to maximize performance by avoiding load-to-store dependencies (even though the AMD optimization guide explicitly says to interleave), or simply because it is not sure that stores won't overwrite loads. Does it change the behavior if you add __restrict keywords to source and dest?

The purpose of the rest of it eludes me too. Could be some obscure instruction decoding or hardware prefetcher considerations, either for AMD or Intel, but I can't find any justification for that. I wonder if the code gets faster or slower when you remove those instructions?

The recommended prefetching distance depends on the loop size. Needs to be far enough that the data has time to arrive from the memory by the time it's needed. I think that you usually need to give it at least 100 clock ticks.

I haven't figured out what the compiler does, however I though I'd share some of my testing results. I've rewritten the function in assembly.

System: Xeon W3520

4.55 GB/s : regular memcpy

5.52 GB/s : memcpy in question

5.58 GB/s : memcpy below

7.48 GB/s : memcpy below multithreaded

void* memcpy(void* dest, const void* source, size_t num)
{   
    __asm
    {
        mov esi, source;    
        mov edi, dest;   

        mov ebx, num; 
        shr ebx, 7;      

        cpy:
            prefetchnta [esi+80h];
            prefetchnta [esi+0C0h];

            movdqa xmm0, [esi+00h];
            movdqa xmm1, [esi+10h];
            movdqa xmm2, [esi+20h];
            movdqa xmm3, [esi+30h];

            movntdq [edi+00h], xmm0;
            movntdq [edi+10h], xmm1;
            movntdq [edi+20h], xmm2;
            movntdq [edi+30h], xmm3;

            movdqa xmm4, [esi+40h];
            movdqa xmm5, [esi+50h];
            movdqa xmm6, [esi+60h];
            movdqa xmm7, [esi+70h];

            movntdq [edi+40h], xmm4;
            movntdq [edi+50h], xmm5;
            movntdq [edi+60h], xmm6;
            movntdq [edi+70h], xmm7;

            lea edi, [edi+80h];
            lea esi, [esi+80h];
            dec ebx;

        jnz cpy;
    }
    return dest;
}

void* memcpy_tbb(void* dest, const void* source, size_t num)
{   
    tbb::parallel_for(tbb::blocked_range<size_t>(0, num/128), [&](const tbb::blocked_range<size_t>& r)
    {
        memcpy_SSE2_3(reinterpret_cast<char*>(dest) + r.begin()*128, reinterpret_cast<const char*>(source) + r.begin()*128, r.size()*128);
    }, tbb::affinity_partitioner());

    return dest;
}
0052AC82  mov         edx,eax        //
0052AC84  movdqa      xmm7,xmmword ptr [edx]  // 3. Why edx? and not simply eax? <--

because it wants propably to split the datapath so this instruction

0052ACAF  sub         eax,0FFFFFF80h  

can be executed in parallel.

Point number 4 could be a hint for the prefetcher...propably (because else it doesn't make any sense, could also be a compiler/optimizer bug/quirk).

I don't have any idea about point 5

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!