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

时间:2014-08-18 16:37:27

标签: c++ multithreading c++11 atomic

我正在尝试在每个可用的硬件线程上运行一个简单的任务(获取当前处理器的x2APIC ID)。我编写了以下代码来执行此操作,该代码适用于我测试它的机器(有关完整的MWE,请参阅here,可在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替换cpuid函数中的asm volatile语句也解决了问题,但我不知道这应该如何必要

我的问题

  1. 在调用CPUID之后调用cpuid和释放栏之前是否应该插入获取栅栏足以阻止编译器执行内存重新排序?
  2. 为什么没有将r4转换为std::atomic<uint32_t>?为什么将前三个输出存储到r1r2r3而不是忽略它们会导致程序工作?
  3. 如何使用最少量的同步来正确编写循环?

1 个答案:

答案 0 :(得分:3)

我已通过启用优化(-O)重现了该问题。你正确怀疑编译器优化。 CPUID作为完整的内存屏障(用于处理器)本身;但它是编译器生成代码而不调用循环中的cpuid函数,因为它威胁它作为常量函数。 asm volatile阻止编译器进行这样的优化,说它有副作用。

有关详细信息,请参阅此答案:https://stackoverflow.com/a/14449998/2527797