【发布时间】:2020-02-03 10:55:46
【问题描述】:
我读了一篇来自Igor's blog 的文章。文章说:
...今天的 CPU 不会逐字节访问内存。相反,它们以(通常)64 字节的块(称为高速缓存行)获取内存。当你读取一个特定的内存位置时,整个缓存行会从主内存中提取到缓存中。而且,从同一缓存行访问其他值很便宜!
文章还提供了c#代码来验证上述结论:
int[] arr = new int[64 * 1024 * 1024];
// Loop 1 (step = 1)
for (int i = 0; i < arr.Length; i++) arr[i] *= 3;
// Loop 2 (step = 16)
for (int i = 0; i < arr.Length; i += 16) arr[i] *= 3;
两个 for 循环在 Igor 的机器上花费大约相同的时间:分别为 80 和 78 ms,因此验证了缓存行机制。
然后我参考上面的思路实现了一个c++版本来验证缓存行大小如下:
#include "stdafx.h"
#include <iostream>
#include <chrono>
#include <math.h>
using namespace std::chrono;
const int total_buff_count = 16;
const int buff_size = 32 * 1024 * 1024;
int testCacheHit(int * pBuffer, int size, int step)
{
int result = 0;
for (int i = 0; i < size;) {
result += pBuffer[i];
i += step;
}
return result;
}
int main()
{
int * pBuffer = new int[buff_size*total_buff_count];
for (int i = 0; i < total_buff_count; ++i) {
int step = (int)pow(2, i);
auto start = std::chrono::system_clock::now();
volatile int result = testCacheHit(pBuffer + buff_size*i, buff_size, step);
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end - start;
std::cout << "step: " << step << ", elapsed time: " << elapsed_seconds.count() * 1000 << "ms\n";
}
delete[] pBuffer;
}
但我的测试结果与 Igor 的文章完全不同。如果 step 为 1,则时间成本约为 114ms;如果步长为 16,则时间成本约为 78ms。测试应用程序是使用发布配置构建的,我的机器上有 32 GB 内存,CPU 是 intel Xeon E5 2420 v2 2.2G;结果如下。
有趣的发现是,当 step 为 2 和 step 为 2048 时,时间成本显着降低。我的问题是,如何解释我的测试中 step 为 2 和 step 为 2048 时的差距?为什么我的结果与 Igor 的结果完全不同?谢谢。
我自己对第一个问题的解释是,代码的时间成本包含两部分:一个是“内存读/写”,其中包含内存读/写时间成本,另一个是“其他成本”,其中包含 for 循环和计算成本。如果 step 是 2,那么“内存读/写”成本几乎没有变化(因为缓存行),但是计算和循环成本减少了一半,所以我们看到了明显的差距。而且我猜我的 CPU 上的缓存线是 4096 字节(1024 * 4 字节)而不是 64 字节,这就是为什么我们在步长为 2048 时出现另一个差距。但这只是我的猜测。感谢您的帮助,谢谢。
【问题讨论】:
-
stdafx.h不是标准的 C++ 标头。见this -
stdafx.h 是项目本身的头文件(用于预编译头文件),所以它当然不是标准头文件
-
ints 在大多数系统上是四个字节。
标签: c++ c caching operating-system