我可以通过给出一个整数的范围来提示优化器吗?

我使用 int类型来存储值。根据程序的语义,值总是在很小的范围内变化(0-36) ,使用 int(而不是 char)仅仅是因为 CPU 效率。

似乎许多特殊的算术优化可以在这么小的整数范围内执行。对这些整数的许多函数调用可以优化为一小组“神奇”操作,有些函数甚至可以优化为表查找。

那么,是否有可能告诉编译器这个 int总是在这个小范围内,编译器是否有可能进行这些优化?

8320 次浏览

是的,有可能。例如,对于 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 程序相似,在发布构建中,它使用一个假设,生成优化的代码。

但是,请注意,它不能替代常规的 assert-cond在版本构建中仍然存在,因此您不应该执行类似于 assume(VeryExpensiveComputation())的操作。

对此有标准的支持。您应该做的是包含 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的第一次调用,与其他调用分开,可能相当大。

但是,请确保将代码分解为单个子例程 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并注意 汇编程序输出中预先计算的常量 0x200x30e

我只是想说,如果您想要一个更标准的 C + + 解决方案,您可以使用 [[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]]函数确实会返回。