我在调试模式下的代码中使用了一个宏:
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);
条件以及依赖它的一切都已经过优化。
我的问题出现在更复杂的条件下。当编译器不能证明条件没有副作用时,它不会优化它,与不使用合同相比,这是一个严重的惩罚。
有没有办法表明合同中的条件没有副作用,因此始终优化了?
答案 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
是我们知道没有副作用的函数,但是编译器没有。左右。
它是这样的:
GCC有__attribute__((pure))
标记函数没有副作用。
使用noSideEffect
属性限定pure
会完全删除函数调用。太好了!
但我们无法修改noSideEffect
的声明。那么如何将它的调用包装在一个本身pure
的函数中呢?因为我们正在尝试制作一个自包含的宏,所以lambda听起来不错。
令人惊讶的是,除非我们将noinline
添加到lambda中,否则它不起作用!我想在优化调用pure
之前,优化器首先内联lambda,在途中丢失noSideEffect
属性。使用noinline
,以一种有点反直觉的方式,优化器能够擦除所有内容。太好了!
但是,现在有两个问题:使用noinline
属性,编译器为每个lambda生成一个主体,即使它们从未被使用过。嗯 - 无论如何,链接器可能会抛弃它们
但更重要的是......实际上我们失去了__builtin_unreachable()
启用的优化:(
总结一下,您可以在上面的代码段中删除或放回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.