Оператор if-statement vs function pointer
Цель состоит в том, чтобы изменить поведение в цикле событий, в зависимости от того, включен или выключен флажок. Самый простой способ, о котором я могу думать, это просто проверить состояние флажка каждый раз, когда цикл запущен.
// 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
}
Ответы
Ответ 1
-
Один косвенный вызов функции более дорогой, чем одно условие.
-
Несколько, если условия дороже, чем вызов косвенной функции.
-
Беспокойство о скорости в этот момент бессмысленно:
Вы ждали латентности пользователя, и вы обрабатываете вещи, на которые он может смотреть (т.е. Не будет большого количества флажков). Оптимизация кода, который выполняется менее миллиона раз в секунду на подробном уровне, как это абсолютно бессмысленно.
Итак, мой совет: перестаньте беспокоиться о стоимости вызова if
или функции во время программирования пользовательского интерфейса. Только подумайте о таких вещах в ваших трудоемких алгоритмах.
Однако, если вы обнаружите, что действительно используете сложные трассировки if
/else
и/или switch
внутри внутреннего цикла, вы можете оптимизировать их, заменив их на косвенные вызовы функций.
Edit:
Вы говорите, что у вас 600 проверок в секунду. Предполагая, что вам нужно обработать только один случай if
(ситуация, когда if
быстрее), вы "сохраняете" примерно 6 микросекунд в секунду, не используя указатель указателя функции, что составляет 0,0006% от времени выполнения. Определенно не стоит усилий...
Ответ 2
Не имея условной ветки, очевидно, сэкономит некоторое время, конечно, она просто разветвляется по ветке, так что у вас возникает поток флеша, это случай, возможно, для двух флешей вместо одного (например, для оптимизации процессора запрета) плюс дополнительный код для сравнения.
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!");
Ответ 4
Чтобы получить точные результаты, необходимы измерения времени, но:
Я уверен, что if имеет меньше накладных расходов, чем полный вызов функции.
- > Если быстрее
Ответ 5
Реализация указателя функции - это IMO. Не очевидно, что там происходит, поэтому вам следует избегать этого.
Возможно, другой вариант - поставить указатели на функцию проверки/действия в массив. И проверьте его внутри цикла. У вас есть чистая функциональность someLoop
, и ваш чек/действие ( "бизнес-логика" ) находится внутри этого массива.
Ответ 6
Если вы только собираетесь проверять эти вещи в одном центральном месте, я рекомендую использовать switch
, если возможно, if/else
, если нет.
Для начала на самом деле проще расширить и добавить новые случаи, если они вам нужны, чем писать новую функцию, проверить что-то и использовать указатель на функцию. Решение указателя функции становится легче расширяться, если вы в противном случае пытались проверить условия в нескольких местах.
Но во-вторых (и что менее важно), он будет иметь тенденцию быть более эффективным, иногда значительно таким образом, и способами, выходящими за рамки простого сравнения стоимости динамической отправки и локального перехода/перехода. Это потому, что вы даете оптимизатору гораздо больше информации для работы.
Я однажды нашел случай, когда GCC 5.3 удалось скрутить сложный набор switch cases
, включающий в себя несколько вызовов функций, в каждом случае, в единую таблицу без разбора, в которой данные загружаются в регистры. Я ожидал таблицу перехода, но она пропустила весь стол перехода и только что выяснила, какие данные нужно загружать для каждого случая без разветвления вообще.
Я был так впечатлен, что даже не осознавал, что все эти функции вызывали cases
, которые он мог просто вскипеть, чтобы загрузить данные из LUT. Но я уверен, что он не смог бы вырезать мой код, как если бы были задействованы косвенные функции. Они в конечном итоге выступают в качестве барьеров оптимизации, если оптимизатор не настолько умен, что может генерировать все пути кода для всех возможных типов указателей функций, которые могут быть вызваны при определении того, какие функции будут вызываться через данный FP.