我试图找出顺序/随机存储器读/写的存储器访问时间。这是代码:
#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);
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
吗?
答案 0 :(得分:3)
听起来很正常。所有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)为单位,基本10 GB = 10 ^ 9是正常的。)
因此,基本10 GB中为4.23 GB / s。
是的,您首先初始化了数组,因此定时运行都不会触发页面错误,但是如果CPU尚未预热到最大涡轮,我可能仍会使用第二次运行。
但是请记住,这是未优化的代码。那就是您未优化的代码运行的速度,并且并不能告诉您太多内存的速度。可能是受CPU限制,而不是内存。
特别是其中有一个冗余& N
,以匹配rndR/W
函数的CPU工作。硬件预取可能能够保持4GB / s的速度,但每个时钟周期甚至都无法读取1 int
。