B.Stroustrup新书中的优化和多线程

Optimization and multithreading in B.Stroustrup's new book

本文关键字:多线程 优化 Stroustrup 新书中      更新时间:2023-10-16

请参阅B.Stroustrup的"TCPL"第4版的41.2.2指令重新排序部分,我将其转录如下:

为了获得性能,编译器、优化器和硬件重新排序说明书考虑:

// thread 1:
int x;
bool x_init;
void init()
{
x = initialize(); // no use of x_init in initialize()
x_init = true;
// ...
}

对于这段代码,之前没有说明分配给x的原因赋值给x_init。优化器(或硬件指令调度器)可以决定通过执行x_init来加速程序=首先是真的。我们可能是想让x_init指示x是否是否已由initializer()初始化。然而,我们没有说所以硬件、编译器和优化器都不知道那个

向程序中添加另一个线程:

// thread 2:
extern int x;
extern bool x_init;
void f2()
{
int y;
while (!x_init) // if necessary, wait for initialization to complete
this_thread::sleep_for(milliseconds{10});
y = x;
// ...
}

现在我们有一个问题:线程2可能永远不会等待,因此将分配一个未初始化x到y。即使线程1没有在中设置x_init和x"订单错误","我们可能还是有问题。"。在线程2中没有对x_init的赋值,因此优化器可能会决定提升的评估!x_init退出循环,因此线程2要么永远不会要么睡,要么永远睡。

  1. 标准是否允许在线程1中重新排序?(即将引用《标准》中的一些话)为什么这会加快项目进度
  2. 关于SO的讨论中的两个答案似乎都表明,当代码中存在全局变量时,不会发生这样的优化,如上面的x_init
  3. 作者所说的"将!x_init的求值从循环中取出"是什么意思?这是这样的东西吗?

    if( !x_init ) while(true) this_thread::sleep_for(milliseconds{10});
    y = x;
    

这与其说是C++编译器/标准的问题,不如说是现代CPU的问题。看看这里。除非你告诉它,否则编译器不会在x和x_init的赋值之间发出内存屏障指令。

值得一提的是,在C++11之前,该标准的抽象机器模型中没有多线程的概念。这几天情况好多了。

  1. C++11标准不"允许"或"阻止"重新排序。它指定了某种方式来强制特定的"屏障",从而阻止编译器在指令之前/之后移动指令。在这个例子中,编译器可能会重新排序赋值,因为它在具有多个计算单元(ALU/超线程等)的CPU上可能更高效,即使只有一个内核。通常,如果您的CPU有两个可以并行工作的ALU,那么编译器没有理由不尝试为它们提供尽可能多的工作。我并不是说在英特尔CPU内部完成的CPU指令的无序重新排序(例如),而是编译时排序,以确保所有计算资源都忙于做一些工作。

  2. 我认为这取决于编译器编译标志。通常,除非你告诉它,否则编译器必须假设另一个编译单元(比如B.cpp,它在编译时不可见)可以有一个"extern bool x_init",并且可以随时更改它。然后,重新排序的优化将与预期的行为决裂(B可以定义initialize()函数)。这个例子微不足道,不太可能被打破。链接的SO答案与这种"优化"无关,只是简单地说,在这种情况下,编译器不能假设全局数组没有被外部修改,因此也不能进行优化。这不像你的例子。

  3. 是的。这是一个非常常见的优化技巧,而不是:

//测试是一个bool

for (int i = 0; i < 345; i++) {
if (test) do_something();
}

编译器可能会这样做:

if (test) for(int i = 0; i < 345; i++) { do_something(); }

并节省344个无用的测试。