对于上下文,此问题与Cache Processor Effects上的博客文章相关,特别是示例1-2。
在下面的代码片段中,我每次都将步长增加2,也就是说,我每次执行的操作数都减少2倍。从博客文章中,我预计步长为1-16时,完成循环的平均时间大致相同。2作者讨论的主要直觉是1)大部分时间是由内存访问贡献的(即先取数,然后乘),而不是算术运算,2)每次CPU取一个缓存行(即64字节或16整数)。
我试着用下面的代码在我的本地机器上重复这个实验,注意我为每个步长分配了一个新的int数组,这样它们就不会利用之前缓存的数据,出于类似的原因,我也只“重复”了每个步长的内部for循环一次(而不是重复实验多次)。
constexpr long long size = 64 * 1024 * 1024; // 64 MB
for (int step = 1; step <= 1<<15 ; step <<= 1) {
auto* arr = new int[size];
auto start = std::chrono::high_resolution_clock::now();
for (size_t i = 0; i < size; i += step) {
arr[i] *= 3;
}
auto finish = std::chrono::high_resolution_clock::now();
auto microseconds = std::chrono::duration_cast<std::chrono::milliseconds>(finish-start);
std::cout << step << " : " << microseconds.count() << "ms\n";
// delete[] arr; (updated - see Paul's comment)
}
然而,结果与blog post中描述的结果大不相同。
未优化:叮当声++ -g -标准品=c2a -学究式-墙壁-额外-o一个CPU缓存1.cpp
使用-O3优化clang -g -std=c++2a -Wpedantic -Wall -Wextra -o a cpu缓存1.cpp-O3
请注意,我运行的是Macbook Pro 2019,我的页面大小是4096。从上面的观察,似乎直到1024步长所需的时间大致保持线性。由于每个int是4字节,这似乎与页面的大小有关(即1024*4 = 4096),这让我认为这可能是某种预取/页面相关的优化,即使没有指定优化?
有人知道或解释为什么这些数字会出现吗?
1条答案
按热度按时间4nkexdtk1#
在您的代码中,您调用了
new int[size]
,它本质上是malloc的 Package 器。由于Linux的乐观内存分配策略,内核不会立即分配物理页面/内存给它(请参阅man malloc)。调用
arr[i] *= 3
时,如果页面不在转换查找缓冲区(TLB)中,则会发生页面错误。内核将检查所请求的虚拟页面是否有效,但尚未分配关联的物理页面。内核将为所请求的虚拟页面分配物理页面。对于步骤= 1024,您将请求与
arr
关联的每一页。对于步骤= 2048,您将请求与arr
关联的每隔一页。分配物理页面的行为是您的瓶颈(根据您的数据,逐个分配64 mb的页面需要大约120 ms)。当您将step从1024增加到2048时,现在内核不需要为每个与
arr
关联的虚拟页面分配物理页面,因此运行时间减半。正如@丹尼尔Langr所链接的,您需要“接触”arr的每个元素,或者使用
new int[size]{}
对arr进行零初始化,这样做的目的是强制内核将物理页面分配给arr
的每个虚拟页面。