【发布时间】:2014-10-11 15:29:57
【问题描述】:
我正在尝试在每个可用的硬件线程上运行一个简单的任务(获取当前处理器的 x2APIC ID)。我编写了以下代码来执行此操作,它可以在我测试过的机器上运行(请参阅here 了解完整的 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而不是忽略它们会导致程序运行? - 如何正确编写循环,使用最少的同步?
【问题讨论】:
-
@Anton 是的,可能是
std::ignore导致了问题。但我不明白这是怎么可能的。
标签: c++ multithreading c++11 atomic