【问题标题】:Is it faster in C to use a written jump table or switch statement?在 C 中使用书面跳转表或 switch 语句是否更快?
【发布时间】:2017-12-17 12:38:59
【问题描述】:

所以,我想看看使用函数指针跳转表与使用 switch 语句执行许多类似这样的命令操作之间是否有任何区别。

This is the code to assembly link i made

这也是我的实际代码

enum code {
    ADD,
    SUB,
    MUL,
    DIV,
    REM
};

typedef struct {
    int val;
} Value;


typedef struct {
    enum code ins;
    int operand;
} Op;


void run(Value* arg, Op* func)
{
   switch(func->ins)
   {
     case ADD: arg->val += func->operand; break;
     case SUB: arg->val -= func->operand; break;
     case MUL: arg->val *= func->operand; break;
     case DIV: arg->val /= func->operand; break;
     case REM: arg->val %= func->operand; break;
   }
}

我的问题是,根据该链接或代码中生成的程序集,与在 switch 语句的情况下制作一堆小函数来完成操作和制作一个指向的指针数组有什么区别吗?那些函数并用相同的枚举调用它们?

使用 gcc x86_64 7.1

void add(Value* arg, Op* func)
{
   arg->val += func->operand;
}

static void (*jmptable)(Value*, Op*)[] = {
     &add
}

汇编代码粘贴:

run(Value*, Op*):
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     QWORD PTR [rbp-16], rsi
        mov     rax, QWORD PTR [rbp-16]
        mov     eax, DWORD PTR [rax]
        cmp     eax, 4
        ja      .L9
        mov     eax, eax
        mov     rax, QWORD PTR .L4[0+rax*8]
        jmp     rax
.L4:
        .quad   .L3
        .quad   .L5
        .quad   .L6
        .quad   .L7
        .quad   .L8
.L3:
        mov     rax, QWORD PTR [rbp-8]
        mov     edx, DWORD PTR [rax]
        mov     rax, QWORD PTR [rbp-16]
        mov     eax, DWORD PTR [rax+4]
        add     edx, eax
        mov     rax, QWORD PTR [rbp-8]
        mov     DWORD PTR [rax], edx
        jmp     .L2
.L5:
        mov     rax, QWORD PTR [rbp-8]
        mov     edx, DWORD PTR [rax]
        mov     rax, QWORD PTR [rbp-16]
        mov     eax, DWORD PTR [rax+4]
        sub     edx, eax
        mov     rax, QWORD PTR [rbp-8]
        mov     DWORD PTR [rax], edx
        jmp     .L2
.L6:
        mov     rax, QWORD PTR [rbp-8]
        mov     edx, DWORD PTR [rax]
        mov     rax, QWORD PTR [rbp-16]
        mov     eax, DWORD PTR [rax+4]
        imul    edx, eax
        mov     rax, QWORD PTR [rbp-8]
        mov     DWORD PTR [rax], edx
        jmp     .L2
.L7:
        mov     rax, QWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rax]
        mov     rdx, QWORD PTR [rbp-16]
        mov     esi, DWORD PTR [rdx+4]
        cdq
        idiv    esi
        mov     edx, eax
        mov     rax, QWORD PTR [rbp-8]
        mov     DWORD PTR [rax], edx
        jmp     .L2
.L8:
        mov     rax, QWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rax]
        mov     rdx, QWORD PTR [rbp-16]
        mov     ecx, DWORD PTR [rdx+4]
        cdq
        idiv    ecx
        mov     rax, QWORD PTR [rbp-8]
        mov     DWORD PTR [rax], edx
        nop
.L2:
.L9:
        nop
        pop     rbp
        ret

【问题讨论】:

  • 太宽泛了,您没有指定架构、编译器、环境,但即使有了这些信息,它仍然太宽泛,因为在一台计算机上使用一个编译器进行一次测试的一组结果仅在以下情况下有效范围
  • 问题用 gcc 标记,生成的汇编代码的链接将其指定为 x86_64
  • case ADD: arg->val += func->operand; 你应该至少在你的 switch 中添加中断。 (这也会使其更快......)
  • 它已经是一个跳转表,如果你愿意,你可以添加函数调用开销..
  • 是的,您看到.quad label 的那一行了吗?那就是跳台。它上面的jmp rax 是跳转到它从表中取出的某个标签的指令。

标签: c gcc jump-table


【解决方案1】:

你能看出区别吗?相信编译器(它会比你做这样的微优化)——不要忘记 break 语句。关心算法,不关心这么小的细节。

https://godbolt.org/g/sPxse2

【讨论】:

    【解决方案2】:

    所有这些问题的全面答案:您应该衡量。

    但实际上,我赌的是切换版本。函数调用有开销(在这种情况下它们几乎不能内联),您可以使用 labels as values 来消除它,这是一个常见的编译器扩展*,但您真的应该尝试所有选项并衡量如果这段代码的性能对你很重要。

    否则,请使用您最方便的方式。


    *a switch 可能会生成一个跳转表,相当于您可以从 labels as values 组成的跳转表,但它可以根据特定情况值在不同的实现之间切换和他们的号码

    【讨论】:

      猜你喜欢
      • 2011-10-15
      • 2014-08-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-21
      • 2017-03-29
      • 2013-09-19
      相关资源
      最近更新 更多