我的问题是,这种差异实际上是由于什么造成的?32位进程是否需要跳过一些额外的WOW64障碍才能访问RAM?这与TLB或预取器有关还是...什么?
感谢您的任何见解。
也在Intel论坛上提出了相关问题。
mov eax, [address]
mov [address2], eax
这将32位(4字节)从地址移动到地址2。在64位模式下,64位也是如此。
mov rax, [address]
mov [address2], rax
mov reg, mem
可能需要超过0.5个周期。然而,上面的延迟时间忽略了这一事实(如我上面链接的PDF所述),它假设所有用于mov的数据都已经存在(否则,延迟将增加为从目前所在的位置获取数据所需的时间长度 - 这可能需要几个时钟周期,并且与正在执行的命令完全无关,如PDF第482/C-30页所述)。movdqa xmm1, [address]
movdqa [address2], xmm1
或者如果没有对齐
movdqu xmm1, [address]
movdqu [address2], xmm1
movdqa/movdqu指令的延迟为1,吞吐量为1。因此,这些指令执行所需的时间是普通mov指令的两倍,指令后的等待时间也是普通mov指令的两倍。
还有一点我们甚至没有考虑到的是CPU实际上将指令分成微操作,并且可以并行执行这些操作。现在情况变得非常复杂了……对我来说甚至太复杂了。
无论如何,我从经验中知道,将数据加载到/从xmm寄存器比将数据加载到/从普通寄存器要慢得多,因此你使用xmm寄存器加速传输的想法从一开始就注定失败。事实上,我很惊讶SSE memmove最终与普通memmove的速度差别不大。
我终于找到了答案(Die in Sente的回答是正确的,谢谢)
在下面的代码中,dst和src都是512MB的std::vector。 我正在使用Intel 10.1.029编译器和CRT。
在64位上,下面两种方式都可以:
memcpy(&dst[0],&src[0],dst.size())
和
memcpy(&dst[0],&src[0],N)
其中N之前声明为const size_t N=512*(1<<20);
时,调用
__intel_fast_memcpy
它的主要部分包括:
000000014004ED80 lea rcx,[rcx+40h]
000000014004ED84 lea rdx,[rdx+40h]
000000014004ED88 lea r8,[r8-40h]
000000014004ED8C prefetchnta [rdx+180h]
000000014004ED93 movdqu xmm0,xmmword ptr [rdx-40h]
000000014004ED98 movdqu xmm1,xmmword ptr [rdx-30h]
000000014004ED9D cmp r8,40h
000000014004EDA1 movntdq xmmword ptr [rcx-40h],xmm0
000000014004EDA6 movntdq xmmword ptr [rcx-30h],xmm1
000000014004EDAB movdqu xmm2,xmmword ptr [rdx-20h]
000000014004EDB0 movdqu xmm3,xmmword ptr [rdx-10h]
000000014004EDB5 movntdq xmmword ptr [rcx-20h],xmm2
000000014004EDBA movntdq xmmword ptr [rcx-10h],xmm3
000000014004EDBF jge 000000014004ED80
并且以大约2200兆字节/秒的速度运行。
但在32位系统上
memcpy(&dst[0],&src[0],dst.size())
调用
__intel_fast_memcpy
其中大部分是
004447A0 sub ecx,80h
004447A6 movdqa xmm0,xmmword ptr [esi]
004447AA movdqa xmm1,xmmword ptr [esi+10h]
004447AF movdqa xmmword ptr [edx],xmm0
004447B3 movdqa xmmword ptr [edx+10h],xmm1
004447B8 movdqa xmm2,xmmword ptr [esi+20h]
004447BD movdqa xmm3,xmmword ptr [esi+30h]
004447C2 movdqa xmmword ptr [edx+20h],xmm2
004447C7 movdqa xmmword ptr [edx+30h],xmm3
004447CC movdqa xmm4,xmmword ptr [esi+40h]
004447D1 movdqa xmm5,xmmword ptr [esi+50h]
004447D6 movdqa xmmword ptr [edx+40h],xmm4
004447DB movdqa xmmword ptr [edx+50h],xmm5
004447E0 movdqa xmm6,xmmword ptr [esi+60h]
004447E5 movdqa xmm7,xmmword ptr [esi+70h]
004447EA add esi,80h
004447F0 movdqa xmmword ptr [edx+60h],xmm6
004447F5 movdqa xmmword ptr [edx+70h],xmm7
004447FA add edx,80h
00444800 cmp ecx,80h
00444806 jge 004447A0
它的速度仅为每秒约1350兆字节。
然而
memcpy(&dst[0],&src[0],N)
其中N是之前声明的const size_t N=512*(1<<20);
,在32位上编译为直接调用一个
__intel_VEC_memcpy
0043FF40 movdqa xmm0,xmmword ptr [esi]
0043FF44 movdqa xmm1,xmmword ptr [esi+10h]
0043FF49 movdqa xmm2,xmmword ptr [esi+20h]
0043FF4E movdqa xmm3,xmmword ptr [esi+30h]
0043FF53 movntdq xmmword ptr [edi],xmm0
0043FF57 movntdq xmmword ptr [edi+10h],xmm1
0043FF5C movntdq xmmword ptr [edi+20h],xmm2
0043FF61 movntdq xmmword ptr [edi+30h],xmm3
0043FF66 movdqa xmm4,xmmword ptr [esi+40h]
0043FF6B movdqa xmm5,xmmword ptr [esi+50h]
0043FF70 movdqa xmm6,xmmword ptr [esi+60h]
0043FF75 movdqa xmm7,xmmword ptr [esi+70h]
0043FF7A movntdq xmmword ptr [edi+40h],xmm4
0043FF7F movntdq xmmword ptr [edi+50h],xmm5
0043FF84 movntdq xmmword ptr [edi+60h],xmm6
0043FF89 movntdq xmmword ptr [edi+70h],xmm7
0043FF8E lea esi,[esi+80h]
0043FF94 lea edi,[edi+80h]
0043FF9A dec ecx
0043FF9B jne ___intel_VEC_memcpy+244h (43FF40h)
这段代码的运行速度大约为2100兆字节/秒(并且证明了32位不受带宽限制)。
我撤回了自己写的类似memcpy的SSE代码在32位版本中速度受到了1300兆字节的限制的说法。现在我已经没有任何问题,无论是在32位还是64位模式下,都能够获得超过2GB/s的速度;技巧(如上面的结果所示)是使用非临时存储("streaming")指令(例如_mm_stream_ps
内置函数)。
看起来有点奇怪的是,32位"dst.size()
" memcpy函数最终并没有调用更快的"movnt
"版本(如果你进入memcpy函数,会发现有大量的CPUID
检查和启发式逻辑,例如将要复制的字节数与缓存大小进行比较等等),但至少现在我理解了观察到的行为(它与SysWow64或硬件无关)。
我随口猜测,64位进程正在使用处理器的本机64位内存大小,这优化了内存总线的使用。
感谢积极的反馈!我觉得我可以部分解释一下正在发生的事情。
当仅计时memcpy调用时,使用非临时存储进行memcpy绝对是最快的。
另一方面,如果你正在对一个应用进行基准测试,movdqa存储的好处在于它们将目标内存保留在缓存中。或者至少是适合缓存的部分。
所以,如果你正在设计一个运行时库,并且可以假设调用memcpy的应用程序将立即使用目标缓冲区,那么你将希望提供movdqa版本。这有效地优化掉了从内存返回到CPU的行程,随后的所有指令都会运行得更快。
但另一方面,如果目标缓冲区相对于处理器的缓存来说很大,那么这种优化就不起作用了,movntdq版本将给你带来更快的应用程序基准测试结果。
因此,memcpy的想法在内部将有多个版本。当目标缓冲区与处理器的高速缓存相比较小时,使用movdqa,否则,当目标缓冲区与处理器的高速缓存相比较大时,使用movntdq。听起来这就是32位库中正在发生的事情。
当然,所有这些都与32位和64位之间的差异无关。
我的猜想是,64位库只是不够成熟。开发人员还没有提供该库版本中的两种例程。
void uint8copy(void *dest, void *src, size_t n){
uint64_t * ss = (uint64_t)src;
uint64_t * dd = (uint64_t)dest;
n = n * sizeof(uint8_t)/sizeof(uint64_t);
while(n--)
*dd++ = *ss++;
}//end uint8copy()
完整的文章在这里: http://www.godlikemouse.com/2008/03/04/optimizing-memcpy-routines/
我手头没有参考资料,所以对于时间/指令我并不完全确定,但是我可以给出理论。如果你在32位模式下进行内存移动,你会执行类似于“rep movsd”的操作,每个时钟周期移动一个32位值。在64位模式下,你可以执行“rep movsq”,每个时钟周期移动一个64位值。该指令在32位代码中不可用,因此你需要执行2次rep movsd(每个周期1个)才能达到一半的执行速度。
非常简化,忽略了所有的内存带宽/对齐问题等,但这就是一切开始的地方...