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

时间:2011-04-07 13:54:59

标签: c optimization callback if-statement switch-statement

我正在编写代码中一个性能非常关键的部分,我有一个关于用函数指针数组替换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)语句的速度优势?

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

当我设置好的东西时,我即将对此进行基准测试,但如果有人已经有答案,我就不会打扰。

6 个答案:

答案 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)

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

  1. 跳转比调用便宜得多(必须保存调用符号寄存器,调整堆栈等)。
  2. switch语句中的代码可以使用已缓存在调用者寄存器中的表达式值。
  3. 一个非常高级的编译器可能会确定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 <=
}