我使用 int类型来存储值。根据程序的语义,值总是在很小的范围内变化(0-36) ,使用 int(而不是 char)仅仅是因为 CPU 效率。
int
char
似乎许多特殊的算术优化可以在这么小的整数范围内执行。对这些整数的许多函数调用可以优化为一小组“神奇”操作,有些函数甚至可以优化为表查找。
那么,是否有可能告诉编译器这个 int总是在这个小范围内,编译器是否有可能进行这些优化?
是的,有可能。例如,对于 gcc,您可以使用 __builtin_unreachable告诉编译器不可能的条件,如下所示:
gcc
__builtin_unreachable
if (value < 0 || value > 36) __builtin_unreachable();
我们可以将上面的条件包装在一个宏中:
#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)
像这样使用它:
assume(x >= 0 && x <= 10);
正如您可以看到的 ,gcc基于以下信息执行优化:
#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0) int func(int x){ assume(x >=0 && x <= 10); if (x > 11){ return 2; } else{ return 17; } }
制作:
func(int): mov eax, 17 ret
然而,一个缺点是 如果你的代码打破了这样的假设,你就会得到未定义行为。
即使在调试构建中,它也不会在发生这种情况时通知您。要使用假设更容易地调试/测试/捕获 bug,您可以使用混合假设/断言宏(归功于@David Z) ,如下所示:
#if defined(NDEBUG) #define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0) #else #include <cassert> #define assume(cond) assert(cond) #endif
在调试构建(定义了 NDEBUG 没有)中,它的工作方式与普通的 assert、打印错误消息和 abort‘ ing 程序相似,在发布构建中,它使用一个假设,生成优化的代码。
NDEBUG
assert
abort
但是,请注意,它不能替代常规的 assert-cond在版本构建中仍然存在,因此您不应该执行类似于 assume(VeryExpensiveComputation())的操作。
cond
assume(VeryExpensiveComputation())
对此有标准的支持。您应该做的是包含 stdint.h(cstdint) ,然后使用类型 uint_fast8_t。
stdint.h
cstdint
uint_fast8_t
这告诉编译器,您只使用0-255之间的数字,但是如果使用较大的类型可以提供更快的代码,那么可以自由使用该类型。类似地,编译器可以假定变量的值永远不会超过255,然后进行相应的优化。
当前的答案对于知道 当然范围是什么的情况很有用,但是如果在值超出预期范围时仍然希望获得正确的行为,那么它就不起作用。
对于这种情况,我发现这种方法可以奏效:
if (x == c) // assume c is a constant { foo(x); } else { foo(x); }
这个想法是一个代码-数据的权衡: 你移动1位的 资料(无论是 x == c)到 控制逻辑。 这向优化器暗示 x实际上是一个已知的常量 c,鼓励它内联并优化 foo的第一次调用,与其他调用分开,可能相当大。
x == c
x
c
foo
但是,请确保将代码分解为单个子例程 foo——不要重复代码。
要让这个技术起作用,你需要有点运气——有些情况下,编译器决定不静态地求值,而且它们是随意的。但是当它工作的时候,它工作的很好:
#include <math.h> #include <stdio.h> unsigned foo(unsigned x) { return x * (x + 1); } unsigned bar(unsigned x) { return foo(x + 1) + foo(2 * x); } int main() { unsigned x; scanf("%u", &x); unsigned r; if (x == 1) { r = bar(bar(x)); } else if (x == 0) { r = bar(bar(x)); } else { r = bar(x + 1); } printf("%#x\n", r); }
只要使用 -O3并注意 汇编程序输出中预先计算的常量 0x20和 0x30e。
-O3
0x20
0x30e
我只是想说,如果您想要一个更标准的 C + + 解决方案,您可以使用 [[noreturn]]属性来编写自己的 unreachable。
[[noreturn]]
unreachable
因此,我将重新利用 < strong > deniss’ 优秀的例子来演示:
namespace detail { [[noreturn]] void unreachable(){} } #define assume(cond) do { if (!(cond)) detail::unreachable(); } while (0) int func(int x){ assume(x >=0 && x <= 10); if (x > 11){ return 2; } else{ return 17; } }
如你所见的结果是几乎相同的代码:
detail::unreachable(): rep ret func(int): movl $17, %eax ret
当然,缺点是您会收到一个警告,说 [[noreturn]]函数确实会返回。