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


173

我正在使用一种int类型来存储值。根据程序的语义,该值始终在很小的范围内(0-36)变化,并且int(不是a char)仅由于CPU效率而使用。

似乎可以对这么小的整数范围执行许多特殊的算术优化。这些整数上的许多函数调用可能被优化为一小组“魔术​​”运算,甚至某些函数甚至可能被优化为表查找。

因此,是否可以告诉编译器这int总是在很小的范围内,并且编译器可以进行那些优化?


4
值范围优化存在于许多编译器中,例如。llvm,但我不知道要声明的任何语言提示。
Remus Rusanu

2
请注意,如果您永远不会有负数,那么使用unsigned类型可能会有一点收获,因为编译器更容易使用它们。
user694733 '16

4
@RemusRusanu:Pascal允许您定义子范围类型,例如var value: 0..36;
Edgar Bonet

7
仅因为CPU效率而使用int(而不是char)。 ”这种传统观点通常不是很正确。窄类型有时需要零或符号扩展到整个寄存器宽度,特别是。当用作数组索引时,但有时这是免费的。如果您有这种类型的数组,则缓存占用空间的减少通常会超过其他任何因素。
彼得·科德斯

1
忘了说:int并且unsigned int在大多数具有64位指针的系统上,也需要从32位符号扩展或零扩展到64位。请注意,在x86-64上,对32位寄存器的操作是免费零扩展到64位的(不是符号扩展,但是有符号的溢出是未定义的行为,因此编译器可以根据需要使用64位有符号的数学运算)。因此,您只会看到将零位扩展32位函数args的额外指令,而不是计算结果。您将使用较窄的无符号类型。
彼得·科德斯

Answers:


230

是的,有可能。例如,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

但是,不利的一面是,如果您的代码违反了这样的假设,则会出现不确定的行为

它不会在发生这种情况时通知您,即使在调试版本中也是如此。要更轻松地调试/测试/捕获带有假设的错误,可以使用混合假设/声明宏(@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“荷兰国际集团程序,并在发布版本它利用一个假设的,产生优化代码。

但是请注意,它不能代替常规代码assert- cond仍保留在发行版本中,因此您不应执行assume(VeryExpensiveComputation())


5
@Xofo,没有得到它,在我的示例中,这已经发生了,因为return 2编译器从代码中消除了分支。

6
但是,似乎gcc 不能如OP所预期的那样优化函数以进行魔术操作或查找表。
jingyu9575 '11 / 11/6

19
@ user3528438 __builtin_expect是非严格提示。__builtin_expect(e, c)应该读为“ e最有可能求和为c”,并且对优化分支预测很有用,但它不限e于始终为c,因此不允许优化器丢弃其他情况。看看组装中的分支是如何组织的

6
从理论上讲,可以使用任何无条件地导致未定义行为的代码__builtin_unreachable()
CodesInChaos

14
除非有一些怪癖,我不知道这使这是一个坏主意,这可能是有意义的这种有机结合起来assert,例如,定义assumeassertNDEBUG没有定义,并且__builtin_unreachable()NDEBUG被定义。这样,您就可以在生产代码中获得假设的好处,但是在调试版本中,您仍然可以进行显式检查。当然,您随后必须进行足够的测试,以确保自己能够完全满足该假设。
David Z
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.