If语句与函数指针

问题描述 投票:9回答:6

目标是更改事件循环中的行为,具体取决于是否打开或关闭复选框。我能想到的最简单的方法就是每次运行循环时测试复选框状态。

// if-statement

void action() { /* ... */ }


void someLoop() {

  if (checkboxTrue) {
    action();
  }
  // ... other stuff

}

如果使用函数指针,代码是否会更高效,更清晰或更好?像这样:

// function pointer

void action() { /* ... */ }
void empty() {}
void (*actionPtr)();


void checkboxChanged(int val) {

  if (val == 1)
    actionPtr = &realAction;
  else
    actionPtr = ∅

}

void someLoop() {

  (*actionPtr)();
  // ... other stuff

}
c performance if-statement function-pointers data-oriented-design
6个回答
9
投票
  1. 一个间接函数调用比一个条件更昂贵。
  2. 如果条件比间接函数调用更昂贵,则有几个。
  3. 在这一点上担心速度是没有意义的: 您正在等待用户的延迟,并且您正在处理他可以查看的内容(即,不会有大量的复选框)。在这样的详细级别上优化每秒执行不到一百万次的代码是绝对没有意义的。

所以,我的建议是:在编写用户界面时,不要担心if或函数调用的成本。只考虑耗费时间的算法中的这些东西。

但是,如果您发现在内循环中确实使用复杂的if / else梯形图和/或switch语句,则可以通过使用间接函数调用替换它们来进行优化。


编辑: 你说你每秒有600张支票。假设你只有一个if案例要处理(if更快的情况),你通过不使用函数指针间接来“保存”大约每秒6微秒,这是运行时的0.0006%。绝对不值得努力......


5
投票

没有条件分支显然会节省一些时间,当然它只是围绕一个分支进行分支,因此你需要进行管道刷新,这可能是两次刷新而不是一次(当然,除非处理器优化)加上额外的代码做比较。

extern void fun0 ( unsigned int );
extern void fun1 ( unsigned int );

void (*fun(unsigned int));


void dofun0 ( unsigned int x, unsigned int y )
{
    if(x) fun0(y);
    else  fun1(y);
}

void dofun ( unsigned int y )
{
    fun(y);
}

例如,给出这样的东西

Disassembly of section .text:

00000000 <dofun0>:
   0:   e3500000    cmp r0, #0
   4:   e92d4008    push    {r3, lr}
   8:   e1a00001    mov r0, r1
   c:   1a000002    bne 1c <dofun0+0x1c>
  10:   ebfffffe    bl  0 <fun1>
  14:   e8bd4008    pop {r3, lr}
  18:   e12fff1e    bx  lr
  1c:   ebfffffe    bl  0 <fun0>
  20:   e8bd4008    pop {r3, lr}
  24:   e12fff1e    bx  lr

00000028 <dofun>:
  28:   e92d4008    push    {r3, lr}
  2c:   ebfffffe    bl  0 <fun>
  30:   e8bd4008    pop {r3, lr}
  34:   e12fff1e    bx  lr

如果您仔细制作测试,您应该能够衡量性能差异。这将是一个非常小的差异,但绝对可以衡量。


3
投票

Imho,指针对于调用具有许多参数的例程来说有点清晰:

int good(int a, int b, int c, char *d) { ... }
int bad (int a, int b, int c, char *d) { ... }
int ugly(int a, int b, int c, char *d) { ... }

直接电话:

if (is_good) {
   good(1,2,3,"fire!");
} else if (is_bad) {
   bad(1,2,3,"fire!");
} else {
   ugly(1,2,3,"fire!");
}

间接电话:

if (is_good) {
   f = good;
} else if (is_bad) {
   f = bad;
} else {
   f = ugly;
}

...

f(1,2,3,"fire!");

1
投票

要获得准确的结果,需要进行时间测量,但是: 我确定if的开销比完整的函数调用少。 - >如果更快


1
投票

您的函数指针实现是IMO根本不清楚。现在发生的事情并不明显,所以你应该避免这种情况。

也许不同的选择是将检查/操作函数指针放入数组中。并在循环中检查它。你有一个干净的someLoop功能,你的检查/操作(“业务逻辑”)在这个数组中。


0
投票

如果您只是在一个中心位置检查这些东西,我建议如果可能的话使用switch,如果不是的话,请使用if/else

对于一个开头实际上更容易扩展并添加新案例,如果你需要它们而不是必须编写新函数,检查事物,并使用函数指针。如果您试图在多个位置检查条件,则函数指针解决方案变得更容易扩展。

但其次(并且不那么重要),它往往更有效率,有时甚至更高效,并且不仅仅是比较动态调度与本地分支/跳转的成本。这是因为您为优化器提供了更多信息。

我曾经发现一个案例,GCC 5.3设法将一组复杂的switch cases压缩到一个无分支的查找表中,该cases涉及多个函数调用,这些查询表将加载到寄存器中的数据。我期待一个跳转表,但它跳过整个跳转表,只是想出了每个案例需要加载哪些数据而根本没有分支。

我印象深刻,因为我甚至没有意识到通过所有那些qazxswpoi调用函数,它可以归结为从LUT加载数据。但我很确定,如果涉及间接函数调用,它将不能像我这样压缩我的代码。它们最终用作优化障碍,除非优化器非常智能,它可以为每个可能的类型的函数指针生成所有代码路径,同时确定哪些函数将通过给定的FP调用。

© www.soinside.com 2019 - 2024. All rights reserved.