16

这只是 4.4 之前的 GCC 版本的问题,在 GCC 4.5 中已修复。

是否可以告诉编译器 switch 中使用的变量适合提供的 case 语句?特别是如果它的范围很小并且生成了一个跳转表。

extern int a;
main()
{
        switch (a & 0x7) {   // 0x7  == 111  values are 0-7
        case 0: f0(); break;
        case 1: f1(); break;
        case 2: f2(); break;
        case 3: f3(); break;
        case 4: f4(); break;
        case 5: f5(); break;
        case 6: f6(); break;
        case 7: f7(); break;
        }
}

我尝试 xor'ing 到低位(例如),使用枚举,使用 gcc_unreachable() 无济于事。生成的代码总是检查变量是否在范围内,添加一个无意义的分支条件并移走跳转表计算代码。

注意:这是在解码器的最内层循环中,性能很重要。

看来我不是唯一 一个

没有办法告诉 gcc 永远不会采用默认分支,尽管如果它可以根据早期的条件检查证明该值从未超出范围,它将省略默认分支。

那么,你如何帮助 gcc 证明变量适合并且在上面的示例中没有默认分支?(当然,不添加条件分支。)

更新

  1. 这是在带有 GCC 4.2 的 OS X 10.6 Snow Leopard 上(默认来自 Xcode。)它没有发生在 linux 中的 GCC 4.4/4.3 上(由 Nathon 和 Jens Gustedt 报告。)

  2. 示例中的函数是为了便于阅读,认为它们是内联的或只是语句。在 x86 上进行函数调用很昂贵。

    此外,如注释中所述,该示例属于数据循环(大数据)。

    使用 gcc 4.2/OS X 生成的代码是:

    [...]
    andl    $7, %eax
    cmpl    $7, %eax
    ja  L11
    mov %eax, %eax
    leaq    L20(%rip), %rdx
    movslq  (%rdx,%rax,4),%rax
    addq    %rdx, %rax
    jmp *%rax
    .align 2,0x90
    L20:
    .long   L12-L20
    .long   L13-L20
    .long   L14-L20
    .long   L15-L20
    .long   L16-L20
    .long   L17-L20
    .long   L18-L20
    .long   L19-L20
    L19:
    [...]
    

    问题在于cmp $7, %eax; ja L11;

  3. 好的,我将使用丑陋的解决方案,并为 4.4 以下的 gcc 版本添加一个特殊情况,使用不带开关的不同版本并使用 goto 和 gcc 的 &&label 扩展。

    static void *jtb[] = { &&c_1, &&c_2, &&c_3, &&c_4, &&c_5, &&c_6, &&c_7, &&c_8 };
    [...]
    goto *jtb[a & 0x7];
    [...]
    while(0) {
    c_1:
    // something
    break;
    c_2:
    // something
    break;
    [...]
    }
    

    请注意,标签数组是静态的,因此不会在每次调用时都计算它。

4

6 回答 6

5

也许您可以使用函数指针数组而不是 switch ?

#include <stdio.h>

typedef void (*func)(void);

static void f0(void) { printf("%s\n", __FUNCTION__); }
static void f1(void) { printf("%s\n", __FUNCTION__); }
static void f2(void) { printf("%s\n", __FUNCTION__); }
static void f3(void) { printf("%s\n", __FUNCTION__); }
static void f4(void) { printf("%s\n", __FUNCTION__); }
static void f5(void) { printf("%s\n", __FUNCTION__); }
static void f6(void) { printf("%s\n", __FUNCTION__); }
static void f7(void) { printf("%s\n", __FUNCTION__); }

int main(void)
{
    const func f[8] = { f0, f1, f2, f3, f4, f5, f6, f7 };
    int i;

    for (i = 0; i < 8; ++i)
    {
        f[i]();
    }
    return 0;
}
于 2010-07-14T21:05:28.137 回答
2

我没有尝试,但我不确定它gcc_unreachable是否与__builtin_unreachable. 谷歌搜索这两者,gcc_unreachable似乎被设计为开发 GCC 本身的断言工具,可能包含分支预测提示,__builtin_unreachable而使程序立即未定义——这听起来像是删除了你想要的基本块。

http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-g_t_005f_005fbuiltin_005funreachable-3075

于 2010-09-30T04:47:29.617 回答
2

您是否尝试将switch变量声明为位域?

struct Container {
  uint16_t a:3;
  uint16_t unused:13;
};

struct Container cont;

cont.a = 5;  /* assign some value */
switch( cont.a ) {
...
}

希望这有效!

于 2010-07-14T20:31:28.913 回答
1

从对我们来说似乎很明显的错过编译器优化的角度来看,这个问题当然很有趣,而且我确实花了相当多的时间试图提出一个简单的解决方案,主要是出于个人的好奇。

也就是说,我不得不承认我非常怀疑这条额外的指令是否会在实践中导致可测量的性能差异,尤其是在新的 Mac 上。如果您有大量数据,您将受到 I/O 限制,并且一条指令永远不会成为您的瓶颈。如果您的数据量很小,那么您需要重复执行大量计算,然后单条指令才会成为瓶颈。

您会发布一些代码来表明确实存在性能差异吗?或者描述您使用的代码和数据?

于 2010-07-15T15:02:09.290 回答
1

default也许只是为第一个或最后一个案例使用标签?

于 2010-07-14T20:43:59.070 回答
1

我尝试编译一些简单且与 -O5 和 -fno-inline 相当的东西(我的 f0-f7 函数很简单),它生成了这个:


 8048420:   55                      push   %ebp ;; function preamble
 8048421:   89 e5                   mov    %esp,%ebp ;; Yeah, yeah, it's a function.
 8048423:   83 ec 04                sub    $0x4,%esp ;; do stuff with the stack
 8048426:   8b 45 08                mov    0x8(%ebp),%eax ;; x86 sucks, we get it
 8048429:   83 e0 07                and    $0x7,%eax ;; Do the (a & 0x7)
 804842c:   ff 24 85 a0 85 04 08    jmp    *0x80485a0(,%eax,4) ;; Jump table!
 8048433:   90                      nop
 8048434:   8d 74 26 00             lea    0x0(%esi,%eiz,1),%esi
 8048438:   8d 45 08                lea    0x8(%ebp),%eax
 804843b:   89 04 24                mov    %eax,(%esp)
 804843e:   e8 bd ff ff ff          call   8048400 
 8048443:   8b 45 08                mov    0x8(%ebp),%eax
 8048446:   c9                      leave  

您是否尝试过使用优化级别?

于 2010-07-14T20:47:24.907 回答