使用C ++中的跳转表实现switch语句

时间:2013-02-24 02:08:59

标签: c++ performance switch-statement memory-address

我正在努力提高解析器的速度。而switch-case,有时它很有用,但我发现它仍然很慢。不确定 - 如果C ++支持此功能(地址检查点(附加参数)),那就太棒了!

简单示例:

enum Transport //MOTORBIKE = 1, CAR = 2,  ...  SHIP = 10
Transport foo = //unknown

    switch(foo)
{
    case MOTORBIKE : /*do something*/ break;
    case CAR : /*do something*/ break;
    //////////////////////////
    case SHIP : /*do something*/ break;
}

如果变量 foo SHIP ,则至少该程序必须重新检查该值最多十次! - >它仍然很慢。

如果C ++支持检查点

Transport foo = //unknown
__checkpoint smart_switch;

goto (smart_switch + foo); //instant call!!!

smart_switch + MOTORBIKE : /*do something*/ goto __end;
smart_switch + CAR : /*do something*/ goto __end;
smart_switch + [...] : /*do something*/ goto __end;
////////////////////////////////////////////////////////////
smart_switch + SHIP : /*do something*/ goto __end;

__end : return 0;

它不生成任何跳转表,然后检查每个值。也许它与default情况不太一致。唯一的问题是 smart_switch + CAR - > smart_switch + SHIP 可能有不同的地址,因此如果C ++将它们评估为真实地址,则该过程将失败。因此,在编译编译器时,只需将它们转换为实际地址。

C ++是否支持此功能?它是否大大提高了速度和速度性能

3 个答案:

答案 0 :(得分:7)

你所说的是一个跳转表。跳转表通常是可以传输程序执行控制的相对地址数组。以下是如何实现一个示例:

#include <ctime>
#include <cstdlib>
#include <cstdio>

int main()
{
    static constexpr void* jump_table[] =
    {
        &&print_0, &&print_1, &&print_2,
        &&print_3, &&print_4, &&print_5
    };

    std::srand(std::time(nullptr));
    int v = std::rand();

    if (v < 0 || v > 5)
        goto out;

    goto *jump_table[v];

print_0:
    std::printf("zero\n");
    goto out;
print_1:
    std::printf("one\n");
    goto out;
print_2:
    std::printf("two\n");
    goto out;
print_3:
    std::printf("three\n");
    goto out;
print_4:
    std::printf("four\n");
    goto out;
print_5:
    std::printf("five\n");
    goto out;
out:
    return EXIT_SUCCESS;
}

然而,我严重怀疑两件事。第一个疑问是使用跳转表会使您的程序更快。间接跳转相对昂贵,并且硬件严重预测。如果你只有三个值,那么你最好只使用“if-then-else”语句比较每个值。对于很多稀疏值(即1,100,250,500等),你最好不要进行二进制搜索而不是炸毁表的大小。在任何一种情况下,当涉及到切换语句时,这只是一个巨大的冰山一角。因此,除非您了解所有细节并知道编译器在哪些情况下针对您的特定情况做了错误的事情,否则甚至不要试图将切换更改为其他东西 - 您永远不会超越编译器并且只会使您的程序变慢。

第二个疑问实际上是切换是解析器的瓶颈。最有可能的不是。因此,为了节省大量宝贵的时间,首先尝试分析代码,以确定程序中最慢的部分。通常它会按照以下步骤进行:

  1. 简介并找到瓶颈。
  2. 弄清楚为什么这是一个瓶颈,并想出如何改进速度代码的合理想法。
  3. 尝试改进代码。
  4. 转到步骤#1。
  5. 此循环没有退出。优化是您可以度过一生的事情。在某些时候,你将不得不假设程序足够快并且没有瓶颈:)

    此外,我已经编写了一个更全面的分析,深入(或多或少)详细说明了编译器如何实现switch语句以及何时何时不尝试尝试智能化。请找到文章here

答案 1 :(得分:5)

是C / C ++确实支持此功能,它位于......标准交换机中。我不知道你在哪里知道switch会检查每个值,但你错了。是的我听说有些编译器可以为相当大的案例生成更好的代码(许多变体可能有几百个),但我不认为它是你的。 例如,下面的代码由gcc编译而没有任何优化:

enum E { One, Two, Three, Four, Five };

void func( E e )
{
    int res;
    switch( e ) {
        case One : res = 10; break;
        case Two : res = 20; break;
        case Three : res = 30; break;
        case Four : res = 40; break;
        case Five : res = 50; break;
    }
}

生成以下内容:

_Z4func1E:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    %edi, -20(%rbp)
    movl    -20(%rbp), %eax
    cmpl    $4, %eax
    ja  .L1
    movl    %eax, %eax
    movq    .L8(,%rax,8), %rax
    jmp *%rax
    .section    .rodata
    .align 8
    .align 4
.L8:
    .quad   .L3
    .quad   .L4
    .quad   .L5
    .quad   .L6
    .quad   .L7
    .text
.L3:
    movl    $10, -4(%rbp)
    jmp .L1
.L4:
    movl    $20, -4(%rbp)
    jmp .L1
.L5:
    movl    $30, -4(%rbp)
    jmp .L1
.L6:
    movl    $40, -4(%rbp)
    jmp .L1
.L7:
    movl    $50, -4(%rbp)
    nop
.L1:
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:

正如您所看到的,它只是在不检查每个值的情况下跳转到特定位置。

答案 2 :(得分:-1)

你可以通过枚举

构建一个带有函数指针和索引的数组