2

我试图弄清楚顺序/随机内存读/写的内存访问时间。这是代码:

#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.0with-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吗?

4

1 回答 1

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,因此基本 10 GB = 10^9 对于以 GB/s 为单位的理论最大带宽当然是正常的。)

因此,以 10 GB 为基础为 4.23 GB/s。

是的,您首先初始化了数组,因此定时运行都不会触发页面错误,但是如果 CPU 尚未预热到最大涡轮增压,我可能仍然使用第二次运行。

但请记住,这是未优化的代码。这就是你的未优化代码运行的速度,并没有告诉你你的内存有多快。它可能受 CPU 限制,而不是内存。

尤其是& N那里有一个冗余来匹配rndR/W功能的 CPU 工作。硬件预取可能能够跟上 4GB/s 的速度,但它甚至还没有在int每个时钟周期读取 1 个。

于 2019-07-15T12:16:40.880 回答