C++ 评估顺序优化是否意味着对不同的操作数使用不同的内核?

Can C++ evaluation order optimizations imply using diferent cores for diferent operands?

C++ 表达式没有定义操作数的计算顺序。这是为了潜在的优化。

对于非常简单的情况:

int i = f() + g();

此类优化是否包括在不同内核上评估 f() 和 g()?如果可以进行此类优化,是否意味着评估顺序取决于运行时?

Does such optimizations include evaluating f() and g() on different cores?

是的,即使我怀疑实际情况是否如此:

  • 启动线程成本高
  • 线程代码有约束和数据竞争,不应引入其他线程问题

更可能的优化是使用内联和重新排序指令(某些值可能已经在寄存器中、缓存中...)。

and if such optimizations are possible, does it mean that the order of evaluation is runtime dependent?

我们可以读入evaluation_order

Order of evaluation of any part of any expression, including order of evaluation of function arguments is unspecified (with some exceptions listed below). The compiler can evaluate operands and other subexpressions in any order, and may choose another order when the same expression is evaluated again.

评估顺序可能会在任何评估中发生变化,因此可能取决于运行时间。

"to imply"这个词有非常明确的技术含义,它在日常用语中的用法与技术语言相匹配(只要我们不让大众把理由说出来,就是这样)。言外之意就是"if A, then B too"。这意味着 "if A, then always B too"。这并不意味着 "when the weather's good" :)

这里没有任何含义,因为这里 A 是 "evaluation order optimizations" 而 B 是 "using different cores for different operands"。评估顺序优化几乎从不导致使用不同的内核,尽管它们很可能导致在单个伪串行执行线程中使用并行执行单元。现代 CPUs 已经自动进行了很多并行化,一个好的代码生成器确实可以让并行执行单元大放异彩(咳咳,变热)。

现在,如果您问的是操作数 是否可以 在单独的内核上进行评估:通常 - NO。这种转换将要求操作数是相互线程安全的,即它们在任何情况下都不能修改共享状态,因为这是明显的未定义行为。

  1. 编译器可以在有限的情况下证明操作数实际上不修改共享状态。他们必须这样做 "reasoning" 来进行日常优化。别名分析就是其中一个例子。这是积极的。

  2. 考虑到多线程分派的成本,操作数的计算需要大量工作才能分派给工作线程。因此,编译器需要 "prove" 要并行化的工作量使得并行化的开销不会使收益相形见绌。

  3. 编译器可以 - 在非常有限的情况下 - 证明可以添加互斥来保护共享的修改状态,而不会引入死锁。因此,它可以添加互斥量 "on the fly"。实际上,这些将是自旋锁,因为不应停止(阻塞)工作调度线程。

  4. 鉴于同步的开销,编译器还需要证明同步的频率不高,其开销是可以接受的。

将上述所有工作做好到值得付出努力仍然在某种程度上超出了任何单个现有生产编译器的能力,并且是深入研究的主题。有概念验证,但在日常使用中没有。不过,这可能会很快改变。

所以 - 目前(2020 年年中)- 在实践中答案仍然是

唉,我们真的分心了评估顺序未定义的真正原因:它为编译器提供了生成更好代码的机会。更好的 "serial" 代码,即。但事实并非如此:在单个 CPU 线程上运行的 "serial" 代码仍在使用 并行执行单元 。因此,在实践中,编译器可以并且 确实并行化了 "serial" 代码 - 它只是在不涉及多个线程的情况下完成的。评估的重新排序可以实现其他优化,减少寄存器压力,通过更好的指令调度和代码矢量化提高 CPU 执行单元的利用率,减少数据依赖性的影响等。