if (a == 1)
//do something
else if (a == 2)
//do something
else if (a == 3)
//do something
else if (a == 4)
//do something
else if (a == 5)
//do something
else if (a == 6)
//do something
else if (a == 7)
//do something
else if (a == 8)
//do something
现在想象一下,我们知道一个主要是7,我们在一个程序中多次执行这个代码块。是否会将(a == 7)支票移至最高点以改善任何时间的表现?那就是:
if (a == 7)
//do something
else if (a == 1)
//do something
else if (a == 2)
//do something
else if (a == 3)
//do something
等等。它改善了什么,或者只是一厢情愿的想法?
答案 0 :(得分:3)
您可以使用switch
case
来改善计划的效果
switch (a)
{
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 7:
break;
}
答案 1 :(得分:2)
由于按照指定的顺序检查if
条件,是的。它是否可测量(因此,你应该关心)将取决于调用该部分代码的次数。
答案 2 :(得分:1)
想象一下,你去了一家酒店,给了一个号码为7
的房间。
你必须穿过大厅检查每个房间,直到找到号码为7
的房间。
您所花费的时间取决于您在分配之前检查了多少个房间?
是强> ..
但是要知道这一点,在您的情景中,时间差异将非常非常短暂地被注意到。
对于需要检查的数字太多的情况,将一个数字放在开头会多次出现确实可以提高性能。事实上,一些网络协议使用这种方法来比较协议号
答案 3 :(得分:1)
如果编译器无法将构造转换为跳转表,则需要支付一些惩罚,我认为切换/案例实现将被编译为汇编中的跳转表,如果不是作为跳转表然后开关/ case有一个边缘,如果否则。我想这再次取决于架构和编译器。
如果是switch / case,编译器只能根据我们提供的常量(例如连续值)生成asm跳转表。
我在我的机器上运行的测试给了if / else的程序集(不是跳转表),
主: .LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $7, -4(%rbp)
cmpl $1, -4(%rbp)
jne .L2
movl $97, %edi
call putchar
jmp .L3
.L2:
**cmpl $2, -4(%rbp)
jne .L4**
movl $97, %edi
call putchar
jmp .L3
.L4:
**cmpl $3, -4(%rbp)
jne .L5**
movl $97, %edi
call putchar
jmp .L3
.L5:
**cmpl $4, -4(%rbp)
jne .L6**
movl $97, %edi
call putchar
jmp .L3
.L6:
**cmpl $5, -4(%rbp)
jne .L7**
movl $97, %edi
call putchar
jmp .L3
.L7:
**cmpl $6, -4(%rbp)
jne .L8**
movl $97, %edi
call putchar
jmp .L3
.L8:
cmpl $7, -4(%rbp)
jne .L9
movl $97, %edi
call putchar
jmp .L3
.L9:
cmpl $8, -4(%rbp)
jne .L3
movl $97, %edi
call putchar
.L3:
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
但对于开关/案例(跳转表), 主要: .LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $7, -4(%rbp)
cmpl $7, -4(%rbp)
ja .L2
movl -4(%rbp), %eax
movq .L4(,%rax,8), %rax
jmp *%rax
.section .rodata
.align 8
.align 4
.L4:
.quad .L2
.quad .L3
.quad .L5
.quad .L6
.quad .L7
.quad .L8
.quad .L9
.quad .L10
.text
.L3:
movl $97, %edi
call putchar
jmp .L2
.L5:
movl $97, %edi
call putchar
jmp .L2
.L6:
movl $97, %edi
call putchar
jmp .L2
.L7:
movl $97, %edi
call putchar
jmp .L2
.L8:
movl $97, %edi
call putchar
jmp .L2
.L9:
movl $97, %edi
call putchar
jmp .L2
.L10:
movl $97, %edi
call putchar
nop
.L2:
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
从测试中我觉得开关/盒子更好,因为它不必通过前面的条目来找到匹配。
我建议尝试使用gcc -S选项生成程序集以检查asm以查看。
答案 4 :(得分:1)
TL; DR版
对于如此少的数值,速度上的任何差异都将是无法估量的,并且你最好坚持使用更直接,更易于理解的版本。直到您需要开始搜索包含数千到数百万条目的表格,您才会想要比线性有序搜索更聪明的条目。
James Michener版本
尚未提及的另一种可能性是进行分区搜索,如下所示:
if ( a > 4 )
{
if ( a > 6 )
{
if ( a == 7 ) // do stuff
else // a == 8, do stuff
}
else
{
if ( a == 5 ) // do stuff
else // a == 6, do stuff
}
}
else
{
if ( a > 2 )
{
if ( a == 3 ) // do stuff
else // a == 4, do stuff
}
else
{
if ( a == 1 ) // do stuff
else // a == 2, do stuff
}
}
对a
的任何值执行的测试不超过三次。当然,对于a
的任何值,也不会执行 less 三次测试。平均而言,当大多数输入为7
时,应该提供比天真的1-8搜索更好的性能,但是......
与性能相关的所有内容一样,规则是衡量,不要猜测。编写不同的版本,对其进行分析,分析结果。对于如此少的值进行测试,很难获得可靠的数字;你需要为给定的值执行每个方法数千次才能得到有用的非零时间测量(这也意味着方法之间的任何差异都会非常小)。
这样的东西也会受到编译器优化设置的影响。您需要构建不同的优化级别并重新运行测试。
只是为了咯咯笑,我编写了我自己的版本,测量了几种不同的方法:
naive
- 按顺序从1到8的直接测试;
sevenfirst
- 首先检查7,然后检查1 - 6和8;
eightfirst
- 以相反的顺序从8到1进行检查;
partitioned
- 使用上面的分区搜索;
switcher
- 使用switch
语句代替if-else
;
我使用了以下测试工具:
int main( void )
{
size_t counter[9] = {0};
struct timeval start, end;
unsigned long total_nsec;
void (*funcs[])(int, size_t *) = { naive, sevenfirst, eightfirst, partitioned, switcher };
srand(time(NULL));
printf("%15s %15s %15s %15s %15s %15s\n", "test #", "naive", "sevenfirst", "eightfirst", "partitioned", "switcher" );
printf("%15s %15s %15s %15s %15s %15s\n", "------", "-----", "----------", "----------", "-----------", "--------" );
unsigned long times[5] = {0};
for ( size_t t = 0; t < 20; t++ )
{
printf( "%15zu ", t );
for ( size_t f = 0; f < 5; f ++ )
{
total_nsec = 0;
for ( size_t i = 0; i < 1000; i++ )
{
int a = generate();
gettimeofday( &start, NULL );
for ( size_t j = 0; j < 10000; j++ )
(*funcs[f])( a, counter );
gettimeofday( &end, NULL );
}
total_nsec += end.tv_usec - start.tv_usec;
printf( "%15lu ", total_nsec );
times[f] += total_nsec;
memset( counter, 0, sizeof counter );
}
putchar('\n');
}
putchar ('\n');
printf( "%15s ", "average:" );
for ( size_t i = 0; i < 5; i++ )
printf( "%15f ", (double) times[i] / 20 );
putchar ('\n' );
return 0;
}
generate
函数生成从1
到8
的随机数,加权,以便7
出现一半时间。我为每个生成的值运行10000次以获得可测量的时间,生成1000个值。
我不希望各种控制结构之间的性能差异被// do stuff
代码淹没,因此每种情况只会增加一个计数器,例如
if ( a == 1 )
counter[1]++;
这也让我有办法验证我的数字生成器是否正常工作。
我遍历整个序列20次并对结果取平均值。即便如此,数字在不同的运行中也会有所不同,所以不要太信任它们。如果没有别的,他们表明这个级别的变化不会带来巨大的改进。例如:
test # naive sevenfirst eightfirst partitioned switcher
------ ----- ---------- ---------- ----------- --------
0 121 100 118 119 111
1 110 100 131 120 115
2 110 100 125 121 111
3 115 125 117 105 110
4 120 116 125 110 115
5 129 100 110 106 116
6 115 176 105 106 115
7 111 100 111 106 110
8 139 100 106 111 116
9 125 100 136 106 111
10 106 100 105 106 111
11 126 112 135 105 115
12 116 120 135 110 115
13 120 105 106 111 115
14 120 105 105 106 110
15 100 131 106 118 115
16 106 113 116 111 110
17 106 105 105 118 111
18 121 113 103 106 115
19 130 101 105 105 116
average: 117.300000 111.100000 115.250000 110.300000 113.150000
数字以微秒为单位。代码是使用gcc 4.1.2构建的,没有在SLES 10系统 1 上运行优化。
因此,对于1000个值运行每个方法10000次,平均超过20次运行,总变化大约为7微秒。这真的不值得一试。对于仅在8个不同值中搜索并且不会运行超过“多次”的内容,无论使用何种方法,您都不会看到任何可衡量的性能改进。坚持使用最容易阅读和理解的方法。
现在,要搜索包含数百到数千到数百万个条目的表格,您肯定希望使用比线性搜索更智能的东西。
<小时/> 1。不言而喻,但上述结果仅适用于使用此特定编译器构建的此代码并在此特定系统上运行。<登记/>
答案 5 :(得分:0)
应该有一点点差别,但这取决于平台和比较的性质 - 不同的编译器可能会以不同的方式优化这样的东西,不同的架构也会有不同的效果,而且还取决于比较的内容实际上是(例如,如果它是比简单的原始类型比较更复杂的比较)
测试您实际要使用的特定情况可能是一种很好的做法,如果这可能实际上是性能瓶颈。
或者,switch语句(如果可用)对于任何独立于order的值都应具有相同的性能,因为它是使用内存中的偏移量实现的,而不是连续的比较。
答案 6 :(得分:-5)
可能不是,您仍然具有相同数量的条件,并且它们中的任何一个仍然可以评估为true,即使您首先检查a == 7
,所有其他条件都可能为真,因此将进行评估。
如果a == 7
在程序运行时可能会更快地执行,则执行的代码块 - 但基本上你的代码仍然是相同的,具有相同数量的语句。