C 开关还是常数表更好?(嵌入式软件)
我想知道,如果可能的话,是开关还是常数表更有效 例如,什么性能更好:C 开关还是常数表更好?(嵌入式软件),c,arrays,switch-statement,embedded,C,Arrays,Switch Statement,Embedded,我想知道,如果可能的话,是开关还是常数表更有效 例如,什么性能更好: 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:
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];
我展示了一个虚拟示例,但我也有使用开关调用不同函数或函数指针表的代码
该代码是针对8位微型计算机的(我不知道它是否对本主题有任何影响)。它取决于应用程序。我更喜欢开关,因为如果所有的情况都失败了,我们就可以有一个默认的情况。 嗯,你应该考虑拆解编译后的代码,看看实际生成的是什么,但是我希望在第二种情况下你的代码更少,分支更少。 在第一种情况下,有七条赋值语句和一系列跳转(从switch语句中)。在第二个数组中,有一个数组引用和一个赋值。由于您的案例都是连续的,因此很容易处理默认案例:
value = ( input < 6 ) ? INPUT_TO_VALUE_TABLE[input] : default_value;
这里面有很多跳转,因为有七个不同的赋值,根据输入的值,我们必须能够跳转到每一个,然后跳转到开关的末尾
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;
}
附录
C代码(example.C)
第二种方法效率更高,但缺乏溢出保护。除非您确定input
介于0
和6
之间,否则您需要一个防护装置:
if (input >= 0 && input < 7) {
value = INPUT_TO_VALUE_TABLE[input];
}
if(输入>=0&&input<7){
值=输入到值表[输入];
}
如果您需要保护,它将耗尽一些性能优势。使用本地表在cpu周期(CPE)和内存消耗方面肯定要保守得多。只需查看其汇编代码,就可以很容易地推断出它
你可以找到一个额外的解释,这取决于你的时间。像您这样基于0的紧凑表可以有一个单个作为保护:
if ((unsigned)input <= 6) {
value = INPUT_TO_VALUE_TABLE[input];
}
(如果max和min是常量,或者至少可以预先计算max-min,那么速度显然更快)开关不太可能优化到与表查找一样高效的状态。假设表格速度更快可能是安全的,但您必须检查您的特定系统
如果它是一个函数指针数组,其中每个函数指针代表一个案例
,那么它将是另一个故事。因为一个好的编译器很可能会使用相邻的数字输入优化一个开关到这样一个函数指针表中
因为您提到了8位MCU,所以进行手动代码优化可能非常相关。遗憾的是,大多数主流8位MCU编译器在代码优化方面都很差(而且通常在标准遵从性方面也很差)。但是在执行方面(速度、CPU要求…)有什么显著的区别吗?
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;
}
.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
if (input >= 0 && input < 7) {
value = INPUT_TO_VALUE_TABLE[input];
}
if ((unsigned)input <= 6) {
value = INPUT_TO_VALUE_TABLE[input];
}
value = (unsigned)(input - min) <= max - min ? table[input] : default_value;