令人困惑的内存重新排序行为
Confusing Memory Reordering Behavior
我试图在每个可用的硬件线程上运行一个简单的任务(获取当前处理器的x2APIC ID)。我写了下面的代码来做这件事,它可以在我测试它的机器上工作(参见这里获得完整的MWE,可在Linux上编译为c++ 11代码)。
void print_x2apic_id()
{
uint32_t r1, r2, r3, r4;
std::tie(r1, r2, r3, r4) = cpuid(11, 0);
std::cout << r4 << std::endl;
}
int main()
{
const auto _ = std::ignore;
auto nprocs = ::sysconf(_SC_NPROCESSORS_ONLN);
auto set = ::cpu_set_t{};
std::cout << "Processors online: " << nprocs << std::endl;
for (auto i = 0; i != nprocs; ++i) {
CPU_SET(i, &set);
check(::sched_setaffinity(0, sizeof(::cpu_set_t), &set));
CPU_CLR(i, &set);
print_x2apic_id();
}
}
一台机器上的输出(当使用g++编译时,版本4.9.0):
0
2
4
6
32
34
36
38
每次迭代打印一个不同的x2APIC ID,所以事情像预期的那样工作。现在是问题开始的地方。我用以下代码替换了对print_x2apic_id
的调用:
uint32_t r4;
std::tie(_, _, _, r4) = cpuid(11, 0);
std::cout << r4 << std::endl;
这将导致在每次循环迭代时打印相同的ID:
36
36
36
36
36
36
36
36
我猜发生了什么是编译器注意到cpuid
的调用不依赖于循环迭代(即使它确实依赖)。然后编译器通过在循环外提升对CPUID的调用来"优化"代码。为了解决这个问题,我将r4
转换为原子:
std::atomic<uint32_t> r4;
std::tie(_, _, _, r4) = cpuid(11, 0);
std::cout << r4 << std::endl;
此操作未能解决问题。令人惊讶的是,确实解决了这个问题:
std::atomic<uint32_t> r1;
uint32_t r2, r3, r4;
std::tie(r1, r2, r3, r4) = cpuid(11, 0);
std::cout << r4 << std::endl;
…好吧,现在我有点糊涂了。
编辑:用asm volatile
替换cpuid
函数中的asm
语句也解决了这个问题,但我不认为这应该是必要的。
- 在调用
cpuid
之前插入一个获取栅栏,在调用CPUID之后插入一个释放栅栏,不应该足以防止编译器执行内存重排序吗? - 为什么不能将
r4
转换为std::atomic<uint32_t>
?为什么将前三个输出存储到r1
、r2
和r3
中而不是忽略它们会使程序正常工作? - 我如何正确地写循环,使用最少的同步必要的量?
我在启用优化(-O)的情况下重现了这个问题。您怀疑编译器优化是对的。CPUID本身作为全内存屏障(对于处理器);但是它是编译器生成代码而不调用循环中的cpuid
函数,因为它威胁到它是一个常量函数。asm volatile
阻止编译器进行这样的优化,说它有副作用。
查看详细信息:https://stackoverflow.com/a/14449998/2527797
相关文章:
- 合并排序不排序自创建数组类 c++
- 使用 Key 对 C++ 中的哈希映射进行排序. 无法排序
- std::排序为排序自定义对象时出现的向量引发错误
- 队列快速排序不排序和打印随机字符串[C ]
- 插入排序中途排序
- C 排序无法排序一组字符串
- C 选择排序不排序
- 气泡排序不排序
- C++:使用 LSD 基数排序字符串排序崩溃
- C 样式的字符串排序与排序和 qsort
- C++ 快速排序不排序
- 如何对包含 pair<int,int> 元素的向量进行排序?排序是根据比较功能完成的
- Introsort(快速排序+堆排序)实现和复杂性
- 用于排序/操作/排序的最佳替代算法
- 没有严格弱排序的排序集
- STL:指针关联排序容器:排序谓词模板
- 排序和排序有什么区别?
- 寻找c++快速排序/插入排序组合中的错误
- 创建没有重复项的新排序向量
- 冒泡排序不排序最后一个数字与此算法