Оператор 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.