32位和64位进程之间的memcpy性能差异

时间:2008-11-06 16:50:48

标签: windows memory 64-bit cpu 32-bit

我们拥有带有XP64的Core2机器(Dell T5400)。

我们观察到在运行32位进程时, memcpy的性能大约是 1.2GByte /秒;但是在64位进程中是memcpy 达到约2.2GByte / s(或2.4GByte / s) 用英特尔编译器CRT的memcpy)。虽然 最初的反应可能只是解释这一点 因为可用的寄存器更宽 在64位代码中,我们观察到我们自己的memcpy之类 SSE汇编代码(应该使用128位 广泛的加载存储,无论32/64位数 该过程)表现出类似的上限 它实现的复制带宽。

我的问题是,实际上这有什么不同 由于 ? 32位进程必须跳过 一些额外的WOW64箍到RAM?是它的东西 与TLB或prefetchers或...做什么?

感谢您的任何见解。

也在Intel forums上提出。

7 个答案:

答案 0 :(得分:8)

我认为以下内容可以解释它:

要将数据从内存复制到寄存器并返回内存,请执行

mov eax, [address]
mov [address2], eax

这会将32位(4字节)从地址移到地址2。 64位模式下的64位也是如此

mov rax, [address]
mov [address2], rax

这将64位,2字节从地址移动到地址2。根据英特尔的规格,“mov”本身,无论是64位还是32位,延迟为0.5,吞吐量为0.5。延迟是指令通过流水线所需的时钟周期数,吞吐量是CPU在再次接受相同指令之前必须等待的时间。正如您所看到的,它可以在每个时钟周期执行两个mov,但是,它必须在两个mov之间等待半个时钟周期,因此它实际上每个时钟周期只能执行一个mov(或者我错在这里并误解了这些术语?有关详细信息,请参阅PDF here

当然mov reg, mem可能超过0.5个周期,具体取决于数据是在第一级还是第二级缓存中,或者根本不在缓存中,需要从内存中获取。但是,上面的延迟时间忽略了这个事实(正如我在上面链接的PDF状态),它假设已经存在mov所需的所有数据(否则延迟将增加从任何地方获取数据所需的时间。现在 - 这可能是几个时钟周期,完全独立于正在执行的命令,如第482 / C-30页上的PDF所述。

有趣的是,mov是32位还是64位不起作用。这意味着除非内存带宽成为限制因素,否则64位mov的速度与32位mov相同,并且由于在使用64位时将相同数量的数据从A移动到B只需要一半的数量,因此吞吐量可以(理论上)是两倍高(事实上它不是因为记忆不是快速无限制的。)

好的,现在您认为在使用较大的SSE寄存器时,您应该获得更快的吞吐量,对吧? AFAIK xmm寄存器不是256,而是128位宽,BTW(reference at Wikipedia)。但是,您是否考虑过延迟和吞吐量?要移动的数据是否为128位对齐。根据具体情况,您可以使用

移动它
movdqa xmm1, [address]
movdqa [address2], xmm1

或者如果没有对齐

movdqu xmm1, [address]
movdqu [address2], xmm1

嗯,movdqa / movdqu的延迟为1,吞吐量为1.因此,指令执行时间要长两倍,指令后的等待时间是正常mov的两倍。

我们甚至没有考虑到的其他事实是CPU实际上将指令拆分为微操作并且可以并行执行这些操作。现在它变得非常复杂......对我来说太复杂了。

无论如何,我从根据经验向xmm寄存器加载数据的速度比从正常寄存器加载数据要慢得多,因此从第一秒起就注定要加速使用xmm寄存器进行传输的想法。我真的很惊讶,最后SSE的记忆并不比普通记忆慢得多。

答案 1 :(得分:5)

我终于明白了这一点(而参议员的回答是正确的,谢谢)

在下面,dst和src是512 MByte 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 MByte / s运行。

但是在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 MByte / s运行。

<强>无论其

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) 

并以~2100MByte / s运行(证明32位不是带宽有限)。

我撤回了我的说法,即我自己的类似memcpy的SSE代码遭受了损失 类似~1300 MByte / 32bit版本限制;我现在没有任何问题 在32或64位上获得&gt; 2GByte / s;诀窍(如上面的结果提示) 是使用非时间(“流”)商店(例如_mm_stream_ps内在的)。

32位“dst.size()”memcpy最终没有出现,这似乎有点奇怪 调用速度更快的“movnt”版本(如果你进入memcpy那就是最多的版本 令人难以置信的CPUID检查和启发式逻辑,例如比较数字 在它到达你的附近的任何地方之前要用高速缓存大小等复制的字节数 实际数据)但至少我现在理解观察到的行为(并且它是 不是SysWow64或H / W相关的。)

答案 2 :(得分:3)

当然,您需要通过使用调试器进入机器代码来查看在memcpy最内层循环中执行的实际机器指令。其他任何事情都只是猜测。

我的问题是它可能与32位与64位本身没有任何关系;我的猜测是更快的库例程是使用SSE非临时存储编写的。

如果内部循环包含传统加载存储指令的任何变体, 然后必须将目标内存读入机器的缓存,修改并写回。由于该读取完全没有必要 - 正在读取的位被立即覆盖 - 您可以通过使用绕过高速缓存的“非时间”写入指令来节省一半的存储器带宽。这样,目的地存储器就会被写入单向行程而不是往返行程。

我不知道英特尔编译器的CRT库,所以这只是猜测。没有特别的理由说明32位libCRT不能做同样的事情,但是你引用的加速是通过将movdqa指令转换为movnt来实现我所期望的......

由于memcpy没有进行任何计算,因此它总是以读取和写入内存的速度为限。

答案 3 :(得分:1)

我的袖口猜测是64位进程正在使用处理器的本机64位内存大小,这优化了内存总线的使用。

答案 4 :(得分:1)

感谢您的积极反馈!我想我可以部分解释这里发生了什么。

使用memcpy的非临时商店肯定是禁食 如果 你只是为memcpy电话定时。

另一方面,如果您正在对应用程序进行基准测试,则movdqa存储的好处是它们将目标内存保留在缓存中。或者至少是适合缓存的部分。

因此,如果您正在设计运行时库,并且如果您可以假设调用memcpy的应用程序将在memcpy调用之后立即使用目标缓冲区,那么您将需要提供movdqa版本。这有效地优化了从内存返回到跟随movntdq版本的cpu的行程,并且调用之后的所有指令将运行得更快。

但另一方面,如果目标缓冲区与处理器的缓存相比较大,则该优化不起作用,而movntdq版本将为您提供更快的应用程序基准。

所以想法memcpy会有多个版本。当目标缓冲区与处理器缓存相比较小时,请使用movdqa,否则,目标缓冲区与处理器缓存相比较大,请使用movntdq。听起来这就是32位库中发生的事情。

当然,这些都与32位和64位之间的差异无关。

我的猜想是64位库不够成熟。开发人员还没有在该版本的库中提供这两个例程。

答案 5 :(得分:0)

我面前没有参考,所以我对时间/指示并不是绝对正面,但我仍然可以给出理论。如果你在32位模式下进行内存移动,你会做类似“rep movsd”的事情,它会在每个时钟周期移动一个32位值。在64位模式下,您可以执行“rep movsq”,它在每个时钟周期执行一次64位移动。该指令不适用于32位代码,因此您将执行2 x rep movsd(以1个周期为单位)执行速度的一半。

非常简化,忽略所有内存带宽/对齐问题等,但这就是它开始的地方......

答案 6 :(得分:0)

这是一个专门针对64位架构的memcpy例程的例子。

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/