令人困惑的内存重新排序行为

Confusing Memory Reordering Behavior

本文关键字:排序 新排序 内存      更新时间:2023-10-16

我试图在每个可用的硬件线程上运行一个简单的任务(获取当前处理器的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语句也解决了这个问题,但我不认为这应该是必要的。

<标题>我的问题
  1. 在调用cpuid之前插入一个获取栅栏,在调用CPUID之后插入一个释放栅栏,不应该足以防止编译器执行内存重排序吗?
  2. 为什么不能将r4转换为std::atomic<uint32_t> ?为什么将前三个输出存储到r1r2r3中而不是忽略它们会使程序正常工作?
  3. 我如何正确地写循环,使用最少的同步必要的量?

我在启用优化(-O)的情况下重现了这个问题。您怀疑编译器优化是对的。CPUID本身作为全内存屏障(对于处理器);但是它是编译器生成代码而不调用循环中的cpuid函数,因为它威胁到它是一个常量函数。asm volatile阻止编译器进行这样的优化,说它有副作用。

查看详细信息:https://stackoverflow.com/a/14449998/2527797