【发布时间】:2019-11-29 21:38:05
【问题描述】:
我刚刚注意到我的一段代码在复制内存时表现出不同的性能。测试表明,如果目标缓冲区的地址大于源地址,则内存复制性能会下降。听起来很荒谬,但下面的代码显示了不同之处(Delphi):
const MEM_CHUNK = 50 * 1024 * 1024;
ROUNDS_COUNT = 100;
LpSrc := VirtualAlloc(0,MEM_CHUNK,MEM_COMMIT,PAGE_READWRITE);
LpDest := VirtualAlloc(0,MEM_CHUNK,MEM_COMMIT,PAGE_READWRITE);
QueryPerformanceCounter(LTick1);
for i := 0 to ROUNDS_COUNT - 1 do
CopyMemory(LpDest,LpSrc,MEM_CHUNK);
QueryPerformanceCounter(LTick2);
// show timings
QueryPerformanceCounter(LTick1);
for i := 0 to ROUNDS_COUNT - 1 do
CopyMemory(LpSrc,LpDest,MEM_CHUNK);
QueryPerformanceCounter(LTick2);
// show timings
这里的 CopyMemory 基于 MOVSD。结果:
开始内存带宽测试...
LpSrc 0x06FC0000
LpDest 0x0A1C0000
src->dest 传输:5242880000 字节在 1,188 秒 @4,110 GB/s。
dest->src 传输:5242880000 字节在 0,805 秒 @6,066 GB/s。
src->dest 传输:5242880000 字节在 1,142 秒 @4,275 GB/s。
dest->src 传输:5242880000 字节,0,832 秒 @5,871 GB/s。
在两个系统上试过,无论重复多少次,结果都是一致的。
从未见过这样的事情。无法谷歌它。这是一种已知的行为吗?这只是另一个与缓存相关的特性吗?
更新:
以下是页面对齐缓冲区和 MOVSD 正向 (DF=0) 的最终结果:
开始内存带宽测试...
LpSrc 0x06F70000
LpDest 0x0A170000
src->dest 传输:5242880000 字节,0,781 秒 @6,250 GB/s。
dest->src 传输:5242880000 字节,0,731 秒 @6,676 GB/s。
src->dest 传输:5242880000 字节,0,750 秒 @6,510 GB/s。
dest->src 传输:5242880000 字节,0,735 秒 @6,640 GB/s。
src->dest 传输:5242880000 字节,0,742 秒 @6,585 GB/s。
dest->src 传输:5242880000 字节,0,750 秒 @6,515 GB/s。
...等等。
这里的传输速率是恒定的。
【问题讨论】:
-
两个缓冲区是否具有相同的对齐方式? 4k 混叠会是问题吗?也许在一个方向上,dst 在页面内的偏移量比 src 稍低,因此内存消歧可以看到负载无法重新加载存储。但另一方面,它可能会错误地检测到混叠并减少带宽。让您的代码打印地址。另外,您在什么 CPU 硬件上进行了测试?哈斯韦尔?天湖?原子?锐龙? K10?
-
如果反转它们会发生什么?或者在它们之间添加一个睡眠?
-
感谢您的建议。将分配更改为 VirtualAlloc 以进行对齐。输出:
-
测试的 CPU 是 SandyBridge 和 Clovertown
-
@BeeOnRope:
rep movsd只对DF=0(升序地址)快速。我刚刚检查了 Skylake:使用rep movsb复制 4096 个非重叠字节的 1000000 次重复使用cld运行 174M 周期,而使用std运行 4161M 周期,用于页面对齐输入或页面 1 输入(我试过两者都是向下的,两者都很糟糕)。执行的 uops 也证实了它在向后复制时花费了更多的 uops。仅当rep movsd被 SIMD 循环替换时,您的向后复制建议才可行。
标签: performance delphi assembly x86 memory-bandwidth