我正在编写代码中一个性能非常关键的部分,我有一个关于用函数指针数组替换case语句(或if语句)的疯狂想法。
让我证明一下;这是正常版本:
while(statement)
{
/* 'option' changes on every iteration */
switch(option)
{
case 0: /* simple task */ break;
case 1: /* simple task */ break;
case 2: /* simple task */ break;
case 3: /* simple task */ break;
}
}
这是“回调函数”版本:
void task0(void) {
/* simple task */
}
void task1(void) {
/* simple task */
}
void task2(void) {
/* simple task */
}
void task3(void) {
/* simple task */
}
void (*task[4]) (void);
task[0] = task0;
task[1] = task1;
task[2] = task2;
task[3] = task3;
while(statement)
{
/* 'option' changes on every iteration */
/* and now we call the function with 'case' number */
(*task[option]) ();
}
那么哪个版本会更快?函数调用的开销是否超过了正常的switch(或if)语句的速度优势?
当然,后一版本不是那么可读,但我正在寻找我能得到的所有速度。
当我设置好的东西时,我即将对此进行基准测试,但如果有人已经有答案,我就不会打扰。
答案 0 :(得分:5)
我认为在一天结束时你的switch语句将是最快的,因为函数指针具有查找函数和函数调用本身的“开销”。一个开关只是一个jmp表直。它当然取决于不同的东西,只有测试可以给你一个答案。这是我的两分钱。
答案 1 :(得分:2)
如果编译器至少具有基本的优化功能,则应将switch语句编译为branch table,这与函数数组基本相同。
答案 2 :(得分:2)
哪个版本更快取决于。 switch
的朴素实现是一个巨大的if ... else if ... else if ...
构造,意味着它需要平均执行O(n)时间,其中n是案例数。您的跳转表是O(1),因此存在的情况越多,后面的情况越多,跳转表越有可能越好。对于少数情况或对于第一种情况比其他情况更频繁地选择的交换机,天真的实现更好。由于编译器可能会选择使用跳转表,即使您已经编写了一个开关,如果它认为会更快,那么事情也会变得复杂。
了解应该选择哪种方法的唯一方法是对代码进行性能测试。
答案 3 :(得分:2)
首先,我会randomly-pause几次,以确保在此次调度中花费足够的时间来进行优化。
其次,如果是,因为每个分支花费的周期很少,所以你需要一个跳转表来到达所需的分支。 switch
语句存在的原因是建议编译器如果开关值是紧凑的,它可以生成一个。
开关值列表有多长?如果它很短,if-ladder仍然可以更快,特别是如果你把最常用的代码放在顶部。 if-ladder(我从未见过任何人使用过)的替代方法是if-tree,相当于二叉树的代码。
您可能不需要一组函数指针。是的,它是一个获取函数指针的数组引用,但在调用函数时有几个指令的开销,听起来这可能会压低每个函数内的少量内容。
在任何情况下,查看汇编语言,或者在指令级别单步执行,都会让你知道它的效率。
答案 4 :(得分:1)
一个好的编译器会将具有小数值范围的情况的开关编译为单个条件,以查看该值是否在该范围内(有时可以优化),然后是跳转跳转。这几乎肯定会比函数调用(直接或间接)更快,因为:
一个非常高级的编译器可能会确定call-via-function指针只引用一小组静态链接函数之一,从而大大优化了事情,甚至可以消除调用并通过跳转替换它们。但我不会指望它。
答案 5 :(得分:0)
我最近来到这个帖子,因为我想知道同样的事情。我最终花时间尝试了。它当然在很大程度上取决于你正在做什么,但对于我的VM来说它是一个不错的加速(15-25%),并允许我简化一些代码(这可能是很多加速来自的地方)。作为一个例子(为了清晰起见简化了代码),使用for循环可以轻松实现“for”循环:
void OpFor( Frame* frame, Instruction* &code )
{
i32 start = GET_OP_A(code);
i32 stop_value = GET_OP_B(code);
i32 step = GET_OP_C(code);
// instruction count (ie. block size)
u32 i_count = GET_OP_D(code);
// pointer to end of block (NOP if it branches)
Instruction* end = code + i_count;
if( step > 0 )
{
for( u32 i = start; i < stop_value; i += step )
{
// rewind instruction pointer
Instruction* cur = code;
// execute code inside for loop
while(cur != end)
{
cur->func(frame, cur);
++cur;
}
}
}
else
// same with <=
}