1

我正在尝试提高解析器的速度。而且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;
}

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

如果 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++ 是否支持此功能?它是否大大提高了速度和性能?

4

3 回答 3

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 等),最好进行二分搜索而不是扩大表的大小。无论哪种情况,这只是 switch 语句的巨大冰山一角。因此,除非您了解所有细节并知道编译器在您的特定情况下做错了什么,否则甚至不要费心尝试将 switch 更改为其他东西 - 您永远不会比编译器更聪明,只会让您的程序变慢。

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

  1. 剖析并找到瓶颈。
  2. 弄清楚为什么这是一个瓶颈,并想出一个合理的想法来提高代码的速度。
  3. 尝试改进代码。
  4. 转到第 1 步。

并且没有退出这个循环。优化是您可以花费一生去做的事情。在某些时候,您将不得不假设程序足够快并且没有瓶颈:)

此外,我还编写了一份更全面的分析,其中包含关于编译器如何实现 switch 语句以及何时以及何时不尝试超越它们的深入(或多或少)细节。请在此处找到该文章。

于 2013-02-24T02:41:59.413 回答
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:

如您所见,它只是跳到特定位置而不检查每个值。

于 2013-02-24T02:16:08.850 回答
-1

您可以通过枚举构建一个带有函数指针和索引的数组

于 2013-02-24T02:11:49.813 回答