c++ - 编译器优化消除了错误共享的影响。如何?
问题描述
正如Tim Mattson在 OpenMP 介绍中所解释的那样,我正在尝试使用 OpenMP 复制虚假共享的效果。
我的程序执行一个简单的数值积分(有关数学细节,请参阅链接),我已经实现了两个版本,第一个版本应该是缓存友好的,让每个线程保留一个局部变量来累积其索引空间的一部分,
const auto num_slices = 100000000;
const auto num_threads = 4; // Swept from 1 to 9 threads
const auto slice_thickness = 1.0 / num_slices;
const auto slices_per_thread = num_slices / num_threads;
std::vector<double> partial_sums(num_threads);
#pragma omp parallel num_threads(num_threads)
{
double local_buffer = 0;
const auto thread_num = omp_get_thread_num();
for(auto slice = slices_per_thread * thread_num; slice < slices_per_thread * (thread_num + 1); ++slice)
local_buffer += func(slice * slice_thickness); // <-- Updates thread-exclusive buffer
partial_sums[thread_num] = local_buffer;
}
// Sum up partial_sums to receive final result
// ...
而第二个版本让每个线程更新共享中的一个元素std::vector<double>
,导致每次写入都使所有其他线程上的缓存行无效
// ... as above
#pragma omp parallel num_threads(num_threads)
{
const auto thread_num = omp_get_thread_num();
for(auto slice = slices_per_thread * thread_num; slice < slices_per_thread * (thread_num + 1); ++slice)
partial_sums[thread_num] += func(slice * slice_thickness); // <-- Invalidates caches
}
// Sum up partial_sums to receive final result
// ...
问题是我无法看到任何虚假共享的影响,除非我关闭优化。
使用没有优化 (-O0) 的 GCC 8.1 编译我的代码(它必须考虑比上面的代码片段更多的细节)会产生我天真的预期的结果,而使用完全优化 (-O3) 消除了性能方面的任何差异两个版本,如图所示。
对此有何解释?编译器是否真的消除了虚假共享?如果不是,为什么运行优化后的代码效果那么小?
我在使用 Fedora 的 Core-i7 机器上。该图显示平均值,其样本标准差不会向该问题添加任何信息。
解决方案
tl; dr:编译器将您的第二个版本优化为第一个版本。
考虑第二个实现的循环中的代码——暂时忽略它的 OMP/多线程方面。
你在一个 - 中有一个值的增量,std::vector
它必须位于堆上(好吧,直到并包括在 C++17 中)。编译器看到你在循环中添加到堆上的值;这是一个典型的优化候选:它将堆访问从循环中取出,并使用寄存器作为缓冲区。它甚至不需要从堆中读取,因为它们只是添加 - 所以它基本上是你的第一个解决方案。
在 GodBolt 上看到这种情况(带有一个简化的示例) - 注意bar1()
和的代码bar2()
几乎相同,在寄存器中发生累积。
现在,涉及多线程和 OMP 的事实并没有改变上述情况。如果您要使用,比如说,std::atomic<double>
而不是double
,那么它可能已经改变(如果编译器足够聪明,甚至可能不会改变)。
笔记:
- 感谢@Evg 注意到此答案先前版本的代码中存在明显错误。
- 编译器必须能够知道这
func()
也不会改变你的向量的值——或者为了加法的目的而决定这并不重要。 - 这种优化可以看作是强度降低——从堆上的操作到寄存器上的操作——但我不确定这个术语是否适用于这种情况。
推荐阅读
- flutter - Flutter Firebase Push Notification 图像未出现在 IOS 通知面板中
- appium - WebDriverAgent 未在 macOS 中编译
- python - python-redmine 按版本截止日期过滤问题
- python - Python中的广度优先算法问题
- wordpress - 在 Elementor Widget 的自定义 WP 查询中设置 WP 查询 ID
- python - 如何使用 Exchangelib python 获取 Message 的父文件夹名称
- azure - 如何使用 azure powershell 命令获取给定存储帐户名称和资源组的存储帐户 ID?
- fortran - 是否可以看到 MPI_ISEND 不立即返回
- android - 让两种构建风格在 Gradle Kotlin DSL 中使用相同的 res 文件?
- python - Sklearn 额外:KMedoids 缺少“方法”参数