【问题标题】:Is better a switch or a const table? (embedded SW)更好的是开关还是常量表? (嵌入式软件)
【发布时间】:2014-02-06 12:42:55
【问题描述】:

我想知道在可能的情况下,是 switch 还是 const table 更有效?

例如,什么会表现得更好:

switch(input) {
  case 0: value = VALUE_0;
    break;
  case 1: value = VALUE_1;
    break;
  case 2: value = VALUE_2;
    break;
  case 3: value = VALUE_3;
    break;
  case 4: value = VALUE_4;
    break;
  case 5: value = VALUE_5;
    break;
  case 6: value = VALUE_6;
    break;
  default:    
    break;
}

或者是这样的:

const uint8_t INPUT_TO_VALUE_TABLE[N_VALUE] = {
    VALUE_0,
    VALUE_1,
    VALUE_2,
    VALUE_3,
    VALUE_4,
    VALUE_5,
    VALUE_6,
}
...
...
value = INPUT_TO_VALUE_TABLE[input];

我展示了一个虚拟示例,但我也有使用开关调用不同函数或函数指针表的代码。

代码适用于 8bits micro(我不知道它是否对这个主题有任何影响)。

【问题讨论】:

    标签: c arrays switch-statement embedded


    【解决方案1】:

    嗯,你应该考虑反汇编编译后的代码,看看实际生成了什么,但我希望在第二种情况下你最终得到的代码更少,分支也更少。

    在第一种情况下,有七个赋值语句,还有一堆跳转(从switch语句中跳出来)。在第二个中,有一个数组引用和一个赋值。由于您的案例都是连续的,因此很容易处理默认案例:

    value = ( input < 6 ) ? INPUT_TO_VALUE_TABLE[input] : default_value;
    

    让我们看一些程序集。这是使用gcc -S 4.6.3 版本编译的,因此它与您得到的程序集不同,但我们应该得到相同的一般结果。这个答案不会绝对回答在你的情况下哪个会更好的问题;你必须自己做一些测试,但看起来很肯定这张桌子会更好。

    switch 选项:

    我们将从switch开始:

    void switch_input( int input ) {
      switch(input) {
      case 0: value = VALUE_0;
        break;
      case 1: value = VALUE_1;
        break;
      case 2: value = VALUE_2;
        break;
      case 3: value = VALUE_3;
        break;
      case 4: value = VALUE_4;
        break;
      case 5: value = VALUE_5;
        break;
      case 6: value = VALUE_6;
        break;
      default: value = VALUE_DEFAULT;
        break;
      }
    }
    

    这里面有很多跳转,因为有七个不同的赋值,根据input的值,我们必须能够跳转到每一个,然后跳转到switch的末尾.

    switch_input:
    .LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    %edi, -4(%rbp)
        cmpl    $6, -4(%rbp)
        ja  .L2
        movl    -4(%rbp), %eax
        movq    .L10(,%rax,8), %rax
        jmp *%rax
        .section    .rodata
        .align 8
        .align 4
    .L10:
        .quad   .L3
        .quad   .L4
        .quad   .L5
        .quad   .L6
        .quad   .L7
        .quad   .L8
        .quad   .L9
        .text
    .L3:
        movl    $0, value(%rip)
        jmp .L1
    .L4:
        movl    $1, value(%rip)
        jmp .L1
    .L5:
        movl    $2, value(%rip)
        jmp .L1
    .L6:
        movl    $3, value(%rip)
        jmp .L1
    .L7:
        movl    $4, value(%rip)
        jmp .L1
    .L8:
        movl    $5, value(%rip)
        jmp .L1
    .L9:
        movl    $6, value(%rip)
        jmp .L1
    .L2:
        movl    $-1, value(%rip)
        nop
    .L1:
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
    

    表格选项

    table 选项可以只使用一个赋值,而不是有很多我们可以跳转到的代码,我们只需要一个值表。我们也不需要跳进那张桌子;我们只需要计算一个索引,然后无条件地从中加载一个值。

    void index_input( int input ) {
      value = ( input < N_VALUE ) ? INPUT_TO_VALUE_TABLE[input] : VALUE_DEFAULT;
    }
    

    (是的,我们确实应该在那里使用无符号整数,这样我们就知道它不会小于零。)

    index_input:
    .LFB1:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    %edi, -4(%rbp)
        cmpl    $5, -4(%rbp)
        jg  .L13
        movl    -4(%rbp), %eax
        cltq
        movl    INPUT_TO_VALUE_TABLE(,%rax,4), %eax
        jmp .L14
    .L13:
        movl    $-1, %eax
    .L14:
        movl    %eax, value(%rip)
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
    

    附录

    C 代码(example.c)

    int value;
    
    #define N_VALUE 7
    
    #define VALUE_0 0
    #define VALUE_1 1
    #define VALUE_2 2
    #define VALUE_3 3
    #define VALUE_4 4
    #define VALUE_5 5
    #define VALUE_6 6
    #define VALUE_DEFAULT -1
    
    void switch_input( int input ) {
      switch(input) {
      case 0: value = VALUE_0;
        break;
      case 1: value = VALUE_1;
        break;
      case 2: value = VALUE_2;
        break;
      case 3: value = VALUE_3;
        break;
      case 4: value = VALUE_4;
        break;
      case 5: value = VALUE_5;
        break;
      case 6: value = VALUE_6;
        break;
      default: value = VALUE_DEFAULT;
        break;
      }
    }
    
    const int INPUT_TO_VALUE_TABLE[N_VALUE] = {
      VALUE_0,
      VALUE_1,
      VALUE_2,
      VALUE_3,
      VALUE_4,
      VALUE_5,
      VALUE_6
    };
    
    void index_input( int input ) {
      value = ( input < 6 ) ? INPUT_TO_VALUE_TABLE[input] : VALUE_DEFAULT;
    }
    

    程序集(example.s)

    gcc -S生成。

        .file   "example.c"
        .comm   value,4,4
        .text
        .globl  switch_input
        .type   switch_input, @function
    switch_input:
    .LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    %edi, -4(%rbp)
        cmpl    $6, -4(%rbp)
        ja  .L2
        movl    -4(%rbp), %eax
        movq    .L10(,%rax,8), %rax
        jmp *%rax
        .section    .rodata
        .align 8
        .align 4
    .L10:
        .quad   .L3
        .quad   .L4
        .quad   .L5
        .quad   .L6
        .quad   .L7
        .quad   .L8
        .quad   .L9
        .text
    .L3:
        movl    $0, value(%rip)
        jmp .L1
    .L4:
        movl    $1, value(%rip)
        jmp .L1
    .L5:
        movl    $2, value(%rip)
        jmp .L1
    .L6:
        movl    $3, value(%rip)
        jmp .L1
    .L7:
        movl    $4, value(%rip)
        jmp .L1
    .L8:
        movl    $5, value(%rip)
        jmp .L1
    .L9:
        movl    $6, value(%rip)
        jmp .L1
    .L2:
        movl    $-1, value(%rip)
        nop
    .L1:
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
    .LFE0:
        .size   switch_input, .-switch_input
        .globl  INPUT_TO_VALUE_TABLE
        .section    .rodata
        .align 16
        .type   INPUT_TO_VALUE_TABLE, @object
        .size   INPUT_TO_VALUE_TABLE, 28
    INPUT_TO_VALUE_TABLE:
        .long   0
        .long   1
        .long   2
        .long   3
        .long   4
        .long   5
        .long   6
        .text
        .globl  index_input
        .type   index_input, @function
    index_input:
    .LFB1:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    %edi, -4(%rbp)
        cmpl    $5, -4(%rbp)
        jg  .L13
        movl    -4(%rbp), %eax
        cltq
        movl    INPUT_TO_VALUE_TABLE(,%rax,4), %eax
        jmp .L14
    .L13:
        movl    $-1, %eax
    .L14:
        movl    %eax, value(%rip)
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
    .LFE1:
        .size   index_input, .-index_input
        .ident  "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
        .section    .note.GNU-stack,"",@progbits
    

    【讨论】:

      【解决方案2】:

      交换机很可能不太可能被优化为像查找表一样高效的东西。假设该表更快可能是安全的,但您必须检查您的特定系统。

      如果它是一个函数指针数组,其中每个函数指针代表一个case,那将是另一回事了。因为一个像样的编译器很可能会优化具有相邻数字输入的开关到这样的函数指针表中。

      由于您提到了 8 位 MCU,因此进行手动代码优化可能非常相关。遗憾的是,大多数主流 8 位 MCU 编译器在代码优化方面都很差(而且通常在标准合规性方面也很差)。

      【讨论】:

        【解决方案3】:

        第二个效率更高,但缺少溢出保护。除非您确定 input 介于 06 之间,否则您需要一个守卫:

        if (input >= 0 && input < 7) {
            value = INPUT_TO_VALUE_TABLE[input];
        }
        

        如果你需要守卫,它会消耗一些性能优势。

        【讨论】:

          【解决方案4】:

          这取决于应用程序。我更喜欢 switch,因为如果所有案例都失败了,我们可以有一个默认案例来做某事。

          【讨论】:

          • 但是在执行(速度、CPU 要求..)方面有什么显着差异吗?
          【解决方案5】:

          使用本地表在 CPU 周期 (CPE) 和内存消耗方面肯定要保守得多。你可以很容易地推断出它,只需查看它的汇编代码。

          您可以找到here的附加说明

          【讨论】:

            【解决方案6】:

            这取决于,你应该计时。像您这样的基于 0 的紧凑表可以有一个 single if 作为守卫:

            if ((unsigned)input <= 6) {
               value = INPUT_TO_VALUE_TABLE[input];
            }
            

            (因为input 的负值在这里被视为很大的正值)

            如果您不需要后卫,桌子很可能是最快的。如果你需要它......这取决于。

            在某些硬件上,这个用单个 if(而不是 2)检查范围的技巧也很有用:

            value = (unsigned)(input - min) <= max - min ? table[input] : default_value;
            

            (如果 max 和 min 是常量,或者至少可以预先计算 max - min,则显然更快)

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2016-06-20
              • 2015-11-03
              • 1970-01-01
              • 1970-01-01
              • 2011-07-22
              • 2011-02-09
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多