if和switch语句的函数数组的性能

我正在编写一个非常重要的性能代码部分,我有一个关于用函数指针数组替换case语句(或if语句)的疯狂想法。

让我certificate一下; 这是正常版本:

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)语句消除速度?

当然,后一版本不是那么可读,但我正在寻找我能得到的所有速度。

当我设置好东西但是如果有人已经有了答案,我就打算对此进行基准测试。

我认为在一天结束时你的switch语句将是最快的,因为函数指针具有查找函数和函数调用本身的“开销”。 一个开关只是一个直接的jmp表。 它当然取决于不同的东西,只有测试可以给你一个答案。 这是我的两分钱。

如果您的编译器至少具有基本的优化function,那么switch语句应该被编译成一个分支表 ,它与您的函数数组基本相同。

哪个版本更快取决于。 switch的天真实现是一个巨大的if ... else if ... else if ...构造意味着它需要平均执行O(n)时间,其中n是个案数。 您的跳转表是O(1),因此存在的情况越多,后面的情况越多,跳转表越有可能越好。 对于少数情况或对于第一种情况比其他情况更频繁地选择的交换机,天真的实现更好。 由于编译器可能会选择使用跳转表,即使您已经编写了一个开关,如果它认为会更快,那么事情就变得复杂了。

了解应该选择哪种方法的唯一方法是对代码进行性能测试。

首先,我会随机地暂停几次,以确保在这次调度中花费足够的时间甚至打扰优化它。

第二,如果是,由于每个分支花费的周期很少,你需要一个跳转表来到达所需的分支。 switch语句存在的原因是建议编译器如果switch值是紧凑的,它可以生成一个。

开关值列表有多长? 如果它很短,if-ladder仍然可以更快,特别是如果你把最常用的代码放在顶部。 if-ladder(我从未真正见过任何人使用过)的替代方案是if-tree,相当于二叉树的代码。

您可能不需要一组函数指针。 是的,它是一个获取函数指针的数组引用,但是在调用函数时有几个指令的开销,听起来这可能会压低每个函数内的少量内容。

在任何情况下,查看汇编语言,或者在指令级别单步执行,都会让您了解它的效率。

一个好的编译器会将一个具有小数值范围的情况的开关编译为单个条件,以查看该值是否在该范围内(有时可以优化),然后是跳转跳转。 这几乎肯定会比函数调用(直接或间接)更快,因为:

  1. 跳转比调用要便宜得多(它必须保存调用被破坏的寄存器,调整堆栈等)。
  2. switch语句中的代码可以使用已缓存在调用者的寄存器中的表达式值。

非常高级的编译器可能会确定call-via-function指针只引用一小组静态链接函数中的一个,从而大量优化事物,甚至可以消除调用并通过跳转替换它们。 但我不会指望它。

我最近来到这个post,因为我想知道同样的事情。 我最终花时间尝试了。 它当然在很大程度上取决于你正在做什么,但对于我的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 <= }