繁体   English   中英

解释这个 CPU Cache 处理器效果:操作次数呈指数下降,但平均时间没有

[英]Explain this CPU Cache processor effect: Number of operations decreased exponentially but average time does not

对于上下文,此问题与有关缓存处理器效果的博客文章相关,特别是示例 1-2。

在下面的代码片段中,我每次都将步长增加 2,即我执行的操作数每次都减少 2 倍。 从博文中,我希望看到步长为 1-16 时,完成循环的平均时间大致相同。 作者讨论的主要直觉是 1) 大部分时间是由 memory 访问(即我们先取然后乘)而不是算术运算贡献的,2) 每次 cpu 取缓存行(即 64 字节或 16 int)。

我尝试使用以下代码在我的本地机器上复制实验。 请注意,我为每个步长分配了一个新的 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)
    }

然而,结果与博客文章中描述的情况大不相同。

没有优化:clang++ -g -std=c++2a -Wpedantic -Wall -Wextra -oa cpu-cache1.cpp

1 : 222ms
2 : 176ms
4 : 152ms
8 : 140ms
16 : 135ms
32 : 128ms
64 : 130ms
128 : 125ms
256 : 123ms
512 : 118ms
1024 : 121ms
2048 : 62ms
4096 : 32ms
8192 : 16ms
16384 : 8ms
32768 : 4ms

使用-O3 优化 clang++ -g -std=c++2a -Wpedantic -Wall -Wextra -oa cpu-cache1.cpp -O3

1 : 155ms
2 : 145ms
4 : 134ms
8 : 131ms
16 : 125ms
32 : 130ms
64 : 130ms
128 : 121ms
256 : 123ms
512 : 127ms
1024 : 123ms
2048 : 62ms
4096 : 31ms
8192 : 15ms
16384 : 8ms
32768 : 4ms

请注意,我正在使用 Macbook Pro 2019 运行,我的页面大小为 4096。从上面的观察来看,似乎直到 1024 步长,所花费的时间大致保持线性。 由于每个 int 都是 4 个字节,这似乎与页面的大小(即 1024*4 = 4096)有关,这让我觉得这可能是某种预取/页面相关的优化,即使没有指定优化?

有人对为什么会出现这些数字有任何想法或解释吗?

在您的代码中,您调用new int[size] ,它本质上是 malloc 的包装器。由于 Linux 的乐观 memory 分配策略(参见 man malloc),kernel 不会立即为其分配物理页面/内存。

当您调用arr[i] *= 3时发生的情况是,如果页面不在转换查找缓冲区 (TLB) 中,则会发生页面错误。 Kernel 将检查您请求的虚拟页面是否有效,但尚未分配关联的物理页面。 kernel 将为您请求的虚拟页面分配物理页面。

对于 step = 1024,您正在请求与arr关联的每个页面。 对于 step = 2048,您正在请求与arr关联的所有其他页面。

这种分配物理页面的行为是您的瓶颈(根据您的数据,一页一页地分配 64mb 的页面需要大约 120 毫秒)。 当您将步长从 1024 增加到 2048 时,现在 kernel 不需要为关联到arr的每个虚拟页面分配物理页面,因此运行时间减半。

正如@Daniel Langr 所链接的,您需要“触摸” arr 的每个元素或使用new int[size]{}对 arr 进行零初始化。 这样做是强制 kernel 将物理页面分配给arr的每个虚拟页面。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2023 STACKOOM.COM