放宽执行命令的规则

Relaxing rules for execution order

本文关键字:规则 命令 执行      更新时间:2023-10-16

C和C++标准规定执行顺序必须严格遵循源指令顺序。编译器可以自由地按任何顺序计算指令中的子表达式,但不能对用分号或冒号分隔的指令进行重新排序。

例如,在指令中:

A = A + B * C + D * E;

编译器可能会选择在B*C之前执行D*E,或者在末尾添加A。

如果现在将相同的计算拆分为不同的指令:

int t1 = B * C;
t1 += D * E;
A += t1;

在这种情况下,编译器无法在B*C之后评估D*E。

一般来说,这类代码稍微慢一点,因为编译器无法优化特定硬件的CPU指令顺序。

我想做的是相反的事情。例如,如果展开循环的主体显示:

A[0] = B[0] * C[0];
A[1] = B[1] * C[1];
A[2] = B[2] * C[2];
A[3] = B[3] * C[3];

有没有办法告诉编译器,这四条指令可以按任何顺序求值,因为它们是在单独的数据上操作的?即使是一个不可移植的技巧也是受欢迎的。

C和C++标准规定执行顺序必须严格遵循源指令顺序。

不完全是。他们说,程序的可观察行为,即I/O操作和对易失性对象的访问,必须发生,就好像是这样。编译器仍然可以随心所欲地重新排序求值,前提是它不会改变程序的行为。只要你的赋值和乘法运算没有明显的副作用,你的代码就可以重新排序。

然而,当您使用指针或引用时,问题会更大。通常,编译器无法判断ABC指向不同的内存区域,因此必须假设对A的赋值可能会更改稍后使用的BC值之一。因此,它无法重新排序评估。在C中,可以使用restrict告诉编译器它们不重叠,但在标准C++中不存在这样的特性。(如果它们是数组,那么这不是问题,在这种情况下,编译器知道它们不重叠。)

编译器只需要应用"好像"规则。可观察的行为必须保持预期,但如果编译器能够得出结论,这些操作不会相互影响,则可以按任何顺序进行操作。

函数参数的求值顺序在C和C++中是未定义的。所以你可以这样做:

template<typename T> void f(T a, T b)
{
}
f( A[0] = B[0] * C[0], A[1] = B[1] * C[1] );

您可以使用逗号运算符。或者,如果目标平台是多核架构,则可以使用OpenMP。

A[0] = B[0] * C[0],
A[1] = B[1] * C[1],
A[2] = B[2] * C[2],
A[3] = B[3] * C[3];

第页。事实上,用逗号是不对的。因此,显式并行可能是一个答案。或者函数调用,如邻居应答。

这可能有效:

void eval_any_order(...) {}
eval_any_order(
  A[0] = B[0] * C[0],
  A[1] = B[1] * C[1],
  A[2] = B[2] * C[2],
  A[3] = B[3] * C[3]
);

编译器可能已经对它们进行了重新排序——只要结果相同,就可以。