在 C 中使用编写的跳转 table 或 switch 语句是否更快?

Is it faster in C to use a written jump table or switch statement?

所以,我想看看使用函数指针的跳转 table 与使用 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;
   }
}

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

使用 gcc x86_647.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

对所有这些问题的全面回答:你应该衡量。

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

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


*a switch 可能会产生一个跳跃 table 等同于你可以从 标签作为值 组成的东西但是它可以根据特定的案例值及其数量

在不同的实现之间切换

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

https://godbolt.org/g/sPxse2