【问题标题】:Compiler instruction reordering optimizations in C++ (and what inhibits them)C ++中的编译器指令重新排序优化(以及抑制它们的原因)
【发布时间】:2012-03-15 13:26:46
【问题描述】:

我已将我的代码缩减为以下内容,这在保留我感兴趣的编译器输出的同时尽可能简单。

void foo(const uint64_t used)
{
    uint64_t ar[100];
    for(int i = 0; i < 100; ++i)
    {
        ar[i] = some_global_array[i];
    }

    const uint64_t mask = ar[0];
    if((used & mask) != 0)
    {
        return;
    }

    bar(ar); // Not inlined
}

将 VC10 与 /O2 和 /Ob1 一起使用,生成的程序集几乎反映了上述 C++ 代码中的指令顺序。由于本地数组 ar 仅在条件失败时传递给 bar(),否则未使用,我希望编译器优化为以下内容。

if((used & some_global_array[0]) != 0)
{
    return;
}

// Now do the copying to ar and call bar(ar)...

编译器不这样做是因为它在一般情况下很难识别这种优化吗?或者它是否遵循一些禁止它这样做的严格规则?如果是这样,为什么?有什么方法可以提示它这样做不会改变我的程序的语义?

注意:显然,仅通过重新排列代码来获得优化的输出是微不足道的,但我感兴趣的是为什么编译器在这种情况下不会优化,而不是 如何 在这种(有意简化的)情况下这样做。

【问题讨论】:

    标签: c++ visual-c++ optimization compiler-optimization


    【解决方案1】:

    这可能没有得到优化的原因是全局数组。编译器无法事先知道,例如,访问some_global_array[99] 是否会导致生成某种异常/信号,因此它必须执行整个循环。如果全局数组是在同一个编译单元中静态定义的,情况会大不相同。

    例如,在 LLVM 中,全局数组的以下三个定义将产生该函数的截然不同的输出:

    // this yields pretty much what you're seeing
    uint64_t *some_global_array; 
    // this calls memcpy and then performs the conditional check
    uint64_t some_global_array[100] = {0};
    // this calls memset (not memcpy!) on the ar array and then bar directly (no 
    // conditional checks since the array is const and filled with 0s, so the if
    // is always false) 
    const uint64_t some_global_array[100] = {0};
    

    第二个非常令人费解,但它可能只是错过了优化(或者我可能遗漏了其他东西)。

    【讨论】:

    • 对,我也认为全局可能是问题所在,但我仍然像你一样惊讶于第二种情况没有优化,这就是我定义全局的方式。事实上,即使使用 const 全局变量,VC10 仍然会复制然后检查条件。无论如何,我进一步简化为 foo() 接受 3 个额外的 const 整数参数 a、b 和 c,将它们放入大小为 3 的本地数组中,完全取消全局,然后在 used & a 上进行测试。在测试条件之前,它仍然分配了 b & c。有什么想法吗?
    • 谢谢,将密切关注该线程。具有 2 个 int 成员的简单结构也产生与数组相同的结果。在这种情况下,它只会浪费一条 asm 指令,但我仍然不明白为什么它不会交换它们。关于这个笑话,是的,这样做的理由列表最近一直在迅速增长......
    【解决方案2】:

    没有“严格的规则”来控制允许编译器输出哪种汇编语言。如果编译器可以确定由于某些前提条件不需要执行一段代码(因为它没有副作用),那么绝对允许将整个代码短路。

    这种优化在一般情况下可能相当复杂,您的编译器可能不会付出所有努力。如果这是性能关键代码,那么您可以微调您的源代码(按照您的建议)以帮助编译器生成最佳汇编代码。不过,这是一个反复试验的过程,您可能需要为下一个版本的编译器再次这样做。

    【讨论】:

    • 我认为“严格规则”的措辞不好,我想我真正要问的是,只要它可以确定它不会改变程序逻辑,就可以做任何想做的事情,在这种情况下是否有特定的东西阻止它假设分配给本地数组没有副作用,其中一个控制路径只是丢弃而不使用?如果答案只是在一般情况下很难,那么公平,这会让我感到惊讶,因为这似乎是最容易检测到的事情之一。
    • 我唯一能想到的是编译器无法知道是否会与其他线程进行交互,尤其是与全局数组。
    • 所以更简化的情况仍然没有在任何主要编译器上得到优化,according to the LLVM guys 正如你所说的一般情况下太难了。
    猜你喜欢
    • 1970-01-01
    • 2017-04-24
    • 1970-01-01
    • 2020-09-25
    • 1970-01-01
    • 2021-01-16
    • 2016-06-23
    • 2020-10-08
    • 1970-01-01
    相关资源
    最近更新 更多