随机内存写入比随机内存读取慢?
Posted
技术标签:
【中文标题】随机内存写入比随机内存读取慢?【英文标题】:Random memory write is slower than random memory read? 【发布时间】:2019-11-24 03:01:51 【问题描述】:我正在尝试计算顺序/随机内存读/写的内存访问时间。代码如下:
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>
#define PRINT_EXCECUTION_TIME(msg, code) \
do \
struct timeval t1, t2; \
double elapsed; \
gettimeofday(&t1, NULL); \
do \
code; \
while (0); \
gettimeofday(&t2, NULL); \
elapsed = (t2.tv_sec - t1.tv_sec) * 1000.0; \
elapsed += (t2.tv_usec - t1.tv_usec) / 1000.0; \
printf(msg " time: %f ms\n", elapsed); \
while (0);
const int RUNS = 20;
const int N = (1 << 27) - 1;
int *data;
int seqR()
register int res = 0;
register int *data_p = data;
register int pos = 0;
for (register int j = 0; j < RUNS; j++)
for (register int i = 0; i < N; i++)
pos = (pos + 1) & N;
res = data_p[pos];
return res;
int seqW()
register int res = 0;
register int *data_p = data;
register int pos = 0;
for (register int j = 0; j < RUNS; j++)
for (register int i = 0; i < N; i++)
pos = (pos + 1) & N;
data_p[pos] = res;
return res;
int rndR()
register int res = 0;
register int *data_p = data;
register int pos = 0;
for (register int j = 0; j < RUNS; j++)
for (register int i = 0; i < N; i++)
pos = (pos + i) & N;
res = data_p[pos];
return res;
int rndW()
register int res = 0;
register int *data_p = data;
register int pos = 0;
for (register int j = 0; j < RUNS; j++)
for (register int i = 0; i < N; i++)
pos = (pos + i) & N;
data_p[pos] = res;
return res;
int main()
data = (int *)malloc(sizeof(int) * (N + 1));
assert(data);
for (int i = 0; i < N; i++)
data[i] = i;
for (int i = 0; i < 10; i++)
PRINT_EXCECUTION_TIME("seqR", seqR());
PRINT_EXCECUTION_TIME("seqW", seqW());
PRINT_EXCECUTION_TIME("rndR", rndR());
PRINT_EXCECUTION_TIME("rndW", rndW());
return 0;
我使用gcc 6.5.0
和-O0
来防止优化,但得到的结果如下:
seqR time: 2538.010000 ms
seqW time: 2394.991000 ms
rndR time: 40625.169000 ms
rndW time: 46184.652000 ms
seqR time: 2411.038000 ms
seqW time: 2309.115000 ms
rndR time: 41575.063000 ms
rndW time: 46206.275000 ms
很容易理解,顺序访问比随机访问要快得多。但是,随机写入比随机读取慢而顺序写入比顺序读取快对我来说没有意义。什么原因会导致这种情况?
此外,我可以肯定地说seqR
的内存带宽是(20 * ((1 << 27) - 1) * 4 * 1024 * 1024 * 1024)GB / (2.538)s = 4.12GB/s
?
【问题讨论】:
你用什么处理器来运行实验? 在我看来data_p[N]
可以在所有四个函数中访问。您可能想要分配 N+1 int
s。
谢谢,应该是N + 1
。我正在使用 Intel Xeon E5-2695 v4 Broadwell。
@zingdle:哦。与四核桌面相比,多核 Xeon 对单线程内存带宽不利是出了名的。不过,4GB/s 仍然低于我的预期,所以我仍然认为您在顺序函数中受 CPU 限制(而不是内存)。见Why is Skylake so much better than Broadwell-E for single-threaded memory throughput?
@PeterCordes 是的,看起来原始代码受 CPU 限制。我将数组更改为volatile
,删除register
并使用O3
重新编译。程序集告诉我它使用一些xmm
寄存器进行优化,但花费的时间与以前大致相同。如果我省略pos = (pos + i) & N;
并使用data[i]
访问数组,则花费的时间减半。但是,我不能像以前那样直接比较随机/顺序内存访问时间。
【参考方案1】:
听起来很正常。所有 x86-64 CPU(以及大多数其他现代 CPU)都使用回写/写分配缓存,因此写入需要先读取一次,然后才能提交到缓存,以及最终的回写。
使用
-O0
防止优化
由于您在所有本地人上都使用了register
,这是罕见的情况之一,这不会使您的基准测试毫无意义。
不过,您可以在数组上使用volatile
,以确保每个访问都按顺序进行,但如何实现则由优化器决定。
我可以肯定地说 seqR 的内存带宽是
(20 * ((1 << 27) - 1) * 4 * 1024 * 1024 * 1024)GB / (2.538)s
=4.12GB/s
?
不,你的分子中有一个额外的因子 2^30 和 10^9。但是你做错了,无论如何都接近了正确的数字。
正确的计算是每秒RUNS * N * sizeof(int) / time
字节,或者除以 10^9 GB/s。或除以 2^30 以获取基本 2 GiB/s。内存大小通常以 GiB 为单位,但您可以选择带宽; DRAM 时钟速度通常为 1600 MHz,因此对于以 GB/s 为单位的理论最大带宽而言,base-10 GB = 10^9 当然是正常的。)
所以 4.23 GB/s(以 10 GB 为基础)。
是的,您首先初始化了数组,因此定时运行都不会触发页面错误,但如果 CPU 尚未预热到最大 turbo,我可能仍会使用第二次运行。
但请记住,这是未优化的代码。这就是你的未优化代码运行的速度,并没有告诉你你的内存有多快。它可能受 CPU 限制,而不是内存。
特别是其中有一个冗余的& N
以匹配rndR/W
函数的CPU 工作。硬件预取可能能够跟上 4GB/s 的速度,但它仍然无法在每个时钟周期读取 1 个int
。
【讨论】:
@HadiBrais:同意,这就是我在回答中这么说的原因:P 但是保持它在那里可能有助于控制 seq 与 rnd 循环中指令计数之间的差异。 我从这个精彩的回答中学到了很多,谢谢!我只是想比较顺序/随机访问之间的性能差异,所以有多余的register
、& N
和O0
。但我仍然不确定为什么顺序写入比读取略快,因为它也使用回写/写入分配。
@zingdle: register
如果您要使用-O0
进行编译,则不是多余的。这是register
关键字有用的一次。使用 GCC,它会阻止变量保存在内存中,并将存储转发存储/重新加载延迟放入循环携带的依赖链中。 (看看生成的 asm;循环结构可能仍然是废话,但至少它没有将循环变量保存在内存中。)
@zingdle:我认为顺序写入更快,因为存储缓冲区可以帮助隐藏偶尔出现的气泡。就像我说的那样,您的代码远没有成为实际内存/缓存带宽的瓶颈,因此硬件预取到 L2 可以轻松跟上。 (除非你的 CPU 是古老的)。您只会在页面边界处停顿。加载必须先完成,然后才能从无序的后端退出,但存储必须在 提交到 L1d 之前退出。 (存储缓冲区将缓存与推测执行隔离开来)。
我在 Haswell 上运行了代码。这些数字接近 OP 显示的(在 Broadwell E5 上),除了观察结果在多次运行中不成立,即 seqR
/rndR
不一定比 seqW
/rndW
快或慢.在许多运行中,差异在 1% 以内,但有时会更大一些。我想你是正确的。顺序循环可能受流水线限制(不是缓存或内存限制),随机循环似乎受至少 L1 上的填充缓冲区数量限制 (L1D_PEND_MISS.FB_FULL
)。在这两种情况下,实现的 BW 都远小于最大单线程 BW。以上是关于随机内存写入比随机内存读取慢?的主要内容,如果未能解决你的问题,请参考以下文章