1

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

这是我制作的组装链接的代码

这也是我的实际代码

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
4

2 回答 2

4

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

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

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


*一个开关可能会生成一个跳转表,相当于你可以从标签组成的值,但它可以根据特定的情况值及其数量在不同的实现之间切换

于 2017-07-12T22:43:50.263 回答
2

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

https://godbolt.org/g/sPxse2

在此处输入图像描述

于 2017-07-12T22:52:56.057 回答