内存分配优化

Memory assignments optimizations

众所周知,编译器可以混合赋值顺序以优化执行, 所以-

a=b;
c=d;

可以实际执行

c=d;
a=b;

但是使用以下代码:

a=b;
x=a;
func(x);

调用 func(x) 时,x 必须先包含 b,否则结果不可预知。

现在,下面的代码呢:

int *addr1 = some_addr;
int *addr2 = (int *)0xf00;

/* The following applies:
 *      some_other_addr >= some_addr
 */
for (addr1; addr1 < some_other_addr; addr1++) 
{
    *addr1 += 1;
}

*addr2 *= 8;

addr2指向for循环范围内的地址时,我们需要知道是否承诺*addr2在乘法前自增8,好像没有,并且一些优化步骤在 for 循环之前放置 *addr2 *= 8;*addr2 的结果将与没有优化的情况下执行的结果不同。

如果 some_addrsome_other_addr 在作用域中定义并且它们作为参数传递,答案会有所不同吗?因为在第一种情况下,编译器很容易知道 *addr2for 循环的范围内,而在第二种情况下则不是那么明显。

此外,如果我们从汇编的角度来看,让我们以 reset_handler 段初始化的示例 reset_handler 代码片段为例:

    ldr  r1, =__BSS_SIZE__
    cmp  r1, #0
    beq  FINISHED_LABEL
    ldr  r0, =__BSS_START__
    ldr  r2, =0
LOOP_LABEL:
    str  r2, [r0]
    add  r0, r4
    subs r1, r4
    bne  LOOP_LABEL

如果此代码之后的下一条指令(在 FINISHED_LABEL)从 bss 范围内的地址加载一个值(ldr),是否承诺内容在以下位置有效(0)那个时候?

编译器必须做的才能做到这一点称为 "alias analysis"

如果编译器可以证明 addr2 不在 addr1 循环的范围内,它可以对其重新排序或在整个循环中将 *addr2 保留在寄存器中。

对于像 for(...; addr1++) { *addr1 += *addr2; } 这样的情况,这是一个非常有用的优化,可以避免每次都重新加载 addr2,这也是 the restrict keyword 存在的原因之一。

如果输入可能重叠,编译器可以(并且)发出检查重叠的代码 并在没有重叠的情况下运行优化的(例如 auto-vectorized)循环,或者在有重叠的情况下运行安全循环。


如果编译器不能证明转换将给出与 C 抽象机相同的最终结果,它就不能进行转换。 (我说 "final" 是因为存储到内存的顺序不是可观察结果的一部分,除非您使用 std::atomic。所以 compile-time 转换不允许破坏 single-threaded 代码,与 out-of-order CPU 所做的非常相似:为单个线程提供一切按程序顺序发生的错觉。)

as-if 规则只允许在 所有 情况下工作但不会导致 UB 的优化,包括模糊unsigned size = 0xffffffff 之类的东西通常会导致编译器无法进行您希望的优化,除非您调整源代码。

UB 是允许某些优化的关键(比如不在循环内重做 sign-extension 有符号数组索引)。参见 每个 C 程序员都应了解的未定义行为#1/3.