如何在没有运行时成本的情况下基于断言来指导GCC优化?

时间:2017-05-18 17:12:45

标签: c++ gcc optimization assert compiler-optimization

我在调试模式下的代码中使用了一个宏:

pstmt.setAsciiStream(1, (InputStream) bis, data.length);

...但在发布模式下:

pstmt.setBinaryStream(1, new ByteArrayInputStream(data));

这对#define contract(condition) \ if (!(condition)) \ throw exception("a contract has been violated"); 的作用是,在发布版本中,由于UB传播,编译器可以大量优化代码。

例如,使用以下代码进行测试:

#define contract(condition) \
    if (!(condition)) \
        __builtin_unreachable();

...在调试模式下抛出异常,但在发布模式下为无条件assert()生成程序集:

int foo(int i) {
    contract(i == 1);
    return i;
}

// ...

foo(0);

条件以及依赖它的一切都已经过优化。

我的问题出现在更复杂的条件下。当编译器不能证明条件没有副作用时,它不会优化它,与不使用合同相比,这是一个严重的惩罚。

有没有办法表明合同中的条件没有副作用,因此始终优化了?

3 个答案:

答案 0 :(得分:5)

所以,不是答案,而是一些可能导致某些问题的有趣结果。

我最终得到了以下玩具代码:

#define contract(x) \
    if (![&]() __attribute__((pure, noinline)) { return (x); }()) \
        __builtin_unreachable();

bool noSideEffect(int i);

int foo(int i) {
    contract(noSideEffect(i));

    contract(i == 1);

    return i;
}

你也可以follow along at home;)

noSideEffect是我们知道没有副作用的函数,但是编译器没有。左右。
它是这样的:

  1. GCC有__attribute__((pure))标记函数没有副作用。

  2. 使用noSideEffect属性限定pure会完全删除函数调用。太好了!

  3. 但我们无法修改noSideEffect的声明。那么如何将它的调用包装在一个本身pure的函数中呢?因为我们正在尝试制作一个自包含的宏,所以lambda听起来不错。

  4. 令人惊讶的是,除非我们将noinline添加到lambda中,否则它不起作用!我想在优化调用pure之前,优化器首先内联lambda,在途中丢​​失noSideEffect属性。使用noinline,以一种有点反直觉的方式,优化器能够擦除所有内容。太好了!

  5. 但是,现在有两个问题:使用noinline属性,编译器为每个lambda生成一个主体,即使它们从未被使用过。嗯 - 无论如何,链接器可能会抛弃它们 但更重要的是......实际上我们失去了__builtin_unreachable()启用的优化:(

  6. 总结一下,您可以在上面的代码段中删除或放回noinline,最后得到以下结果之一:

    使用noinline

    ; Unused code
    foo(int)::{lambda()#2}::operator()() const:
            mov     rax, QWORD PTR [rdi]
            cmp     DWORD PTR [rax], 1
            sete    al
            ret
    foo(int)::{lambda()#1}::operator()() const:
            mov     rax, QWORD PTR [rdi]
            mov     edi, DWORD PTR [rax]
            jmp     noSideEffect(int)
    
    ; No function call, but the access to i is performed
    foo(int):
            mov     eax, edi
            ret
    

    没有noinline

    ; No unused code
    ; Access to i has been optimized out,
    ; but the call to `noSideEffect` has been kept.
    foo(int):
            sub     rsp, 8
            call    noSideEffect(int)
            mov     eax, 1
            add     rsp, 8
            ret
    

答案 1 :(得分:4)

  

有没有办法表明合同中的条件没有副作用,所以它总是被优化出来?

不太可能。

众所周知,你不能收集大量的断言,把它们变成假设(通过__builtin_unreachable)并期望得到好的结果(例如John Regehr的Assertions Are Pessimistic, Assumptions Are Optimistic)。

一些线索:

  • CLANG虽然已经拥有__builtin_unreachable内在函数,但为此目的引入了__builtin_assume

  • N4425 - Generalized Dynamic Assumptions (*)指出:

      

    GCC没有明确提供一般假设,但一般假设可以使用控制流和__builtin_unreachable内在

    的组合进行编码      

    ...

         

    提供通用假设的现有实现在实现保留标识符空间(__assume__builtin_assume等)中使用一些关键字。由于未评估表达式参数(副作用被丢弃),因此根据特殊库函数(例如std::assume)指定此参数似乎很困难。

  • 指南支持库(GSL,由Microsoft托管,但绝不是Microsoft特定的)只有“此代码”:

    #ifdef _MSC_VER
    #define GSL_ASSUME(cond) __assume(cond)
    #elif defined(__clang__)
    #define GSL_ASSUME(cond) __builtin_assume(cond)
    #elif defined(__GNUC__)
    #define GSL_ASSUME(cond) ((cond) ? static_cast<void>(0) : __builtin_unreachable())
    #else
    #define GSL_ASSUME(cond) static_cast<void>(!!(cond))
    #endif
    

    并注意到:

    // GSL_ASSUME(cond)
    //
    // Tell the optimizer that the predicate cond must hold. It is unspecified
    // whether or not cond is actually evaluated.
    

<子> *)文件rejected:EWG的指导是在拟议的合同设施内提供功能。

答案 2 :(得分:2)

如果代码是死代码,则无法强制优化代码,因为GCC必须始终遵守标准。

另一方面,通过使用属性error可以检查表达式没有任何副作用,当函数的调用无法优化时,该属性将显示错误。

一个宏的示例,它检查优化的任何内容并进行UB传播:

#define _contract(condition) \
    {
        ([&]() __attribute__ ((noinline,error ("contract could not be optimized out"))) {
            if (condition) {} // using the condition in if seem to hide `unused` warnings.
        }());
        if (!(condition))
            __builtin_unreachable();
    }

如果没有优化,则error属性不起作用(因此该宏只能用于发布\优化模式编译)。 请注意,在链接期间会显示指示合同有副作用的错误。

A test that shows an error with unoptimizable contract.

A test that optimizes out a contract but, does UB propagation with it.