Указатель функции делает программу медленной?

Я читал об указателях функции в C. И все сказали, что это сделает мой прогон программы медленным. Действительно ли это верно?

Я сделал программу для проверки его. И я получил те же результаты на обоих случаях. (измерьте время.)

Так, это плохо для использования указателя функции?Заранее спасибо.

К ответу для некоторых парней. Я сказал, 'отстают' в течение времени, когда я выдержал сравнение на цикле. как это:

int end = 1000;
int i = 0;

while (i < end) {
 fp = func;
 fp ();
}

Когда Вы выполняете это, я получил то же время, если я выполняю это.

while (i < end) {
 func ();
}

Таким образом, я думаю, что указатель функции не имеет никакого различия времени, и он не делает прогон программы медленным, как сказали многие люди.

52
задан Abhijeet Rastogi 17 March 2010 в 03:47
поделиться

7 ответов

Видите ли, в ситуациях, которые действительно важны с точки зрения производительности, например, при многократном вызове функции за цикл, производительность может не отличаться вообще.

Это может показаться странным для людей, которые привыкли думать о коде C как о чем-то, выполняемом абстрактной машиной C, чей «машинный язык» точно отражает сам язык C. В таком контексте «по умолчанию» косвенный вызов функции действительно медленнее прямого, поскольку формально включает дополнительный доступ к памяти для определения цели вызова.

Однако в реальной жизни код выполняется реальной машиной и компилируется оптимизирующим компилятором, который хорошо знает архитектуру базовой машины, что помогает ему генерировать наиболее оптимальный код для этой конкретной машины. И на многих платформах может оказаться, что наиболее эффективный способ выполнения вызова функции из цикла на самом деле приводит к идентичному коду как для прямого, так и для косвенного вызова, что приводит к идентичной производительности обоих.

Рассмотрим, например, платформу x86. Если мы «буквально» переведем прямой и косвенный вызов в машинный код, мы можем получить что-то вроде этого

// Direct call
do-it-many-times
  call 0x12345678

// Indirect call
do-it-many-times
  call dword ptr [0x67890ABC]

. Первый использует непосредственный операнд в машинной инструкции и действительно обычно быстрее, чем второй, который должен читать данные из некоторой независимой области памяти.

Здесь давайте вспомним, что архитектура x86 на самом деле имеет еще один способ предоставить операнд для инструкции call . Он предоставляет целевой адрес в регистре . И очень важная особенность этого формата заключается в том, что он обычно быстрее, чем оба указанных выше .Что это значит для нас? Это означает, что хороший оптимизирующий компилятор должен и будет использовать этот факт. Чтобы реализовать вышеупомянутый цикл, компилятор попытается использовать вызов через регистр в обоих случаях. В случае успеха окончательный код может выглядеть следующим образом

// Direct call

mov eax, 0x12345678

do-it-many-times
  call eax

// Indirect call

mov eax, dword ptr [0x67890ABC]

do-it-many-times
  call eax

. Обратите внимание, что теперь важная часть - фактический вызов в теле цикла - в обоих случаях совершенно одинакова. Излишне говорить, что производительность будет практически идентичной .

Можно даже сказать, как бы странно это ни звучало, что на этой платформе прямой вызов (вызов с непосредственным операндом в вызов ) выполняется медленнее , чем косвенный вызов, поскольку до тех пор, пока операнд косвенного вызова предоставляется в регистре (в отличие от сохранения в памяти).

Конечно, в общем случае все не так просто. Компилятору приходится иметь дело с ограниченной доступностью регистров, проблемами псевдонимов и т. Д. Но в таких упрощенных случаях, как тот, что в вашем примере (и даже в гораздо более сложных), вышеуказанная оптимизация будет выполнена хорошим компилятором и полностью исключит любая разница в производительности между циклическим прямым вызовом и циклическим косвенным вызовом. Эта оптимизация особенно хорошо работает в C ++ при вызове виртуальной функции, поскольку в типичной реализации задействованные указатели полностью контролируются компилятором, что дает ему полное представление об изображении псевдонимов и других важных вещах.

Конечно, всегда возникает вопрос, достаточно ли умен ваш компилятор, чтобы оптимизировать подобные вещи ...

82
ответ дан 7 November 2019 в 09:05
поделиться

Использование указателя функции происходит медленнее, чем простой вызов функции, поскольку это еще один уровень косвенного обращения. (Чтобы получить адрес памяти функции, необходимо разыменовать указатель). Хотя он и медленнее, по сравнению со всем остальным, что может делать ваша программа (читать файл, записывать в консоль), он незначителен.

Если вам нужно использовать указатели на функции, используйте их, потому что все, что пытается сделать то же самое, но избегает их использования, будет медленнее и менее ремонтопригодно, чем использование указателей на функции.

7
ответ дан 7 November 2019 в 09:05
поделиться

Я думаю, когда люди говорят это, они имеют в виду тот факт, что использование указателей функций может помешать оптимизации компилятора (встраивание) и оптимизации процессора (предсказание ветвлений). Однако, если указатели на функции являются эффективным способом выполнить то, что вы пытаетесь сделать, есть вероятность, что любой другой способ выполнения будет иметь те же недостатки.

И если указатели на функции не используются в жестких циклах в приложении, критичном к производительности, или в очень медленной встроенной системе, скорее всего, разница в любом случае незначительна.

25
ответ дан 7 November 2019 в 09:05
поделиться

Вызов функции через указатель функции несколько медленнее, чем вызов статической функции, поскольку первый вызов включает в себя дополнительное разыменование указателя. Но AFAIK эта разница незначительна на большинстве современных машин (за исключением, возможно, некоторых специальных платформ с очень ограниченными ресурсами).

Указатели на функции используются, потому что они могут сделать программу намного проще, чище и легче в обслуживании (конечно, при правильном использовании). Это более чем компенсирует незначительную разницу в скорости.

8
ответ дан 7 November 2019 в 09:05
поделиться

И все говорили, что из-за этого моя программа будет работать медленно. Это правда?

Скорее всего, это утверждение неверно. Во-первых, если альтернативой использованию указателей на функции является что-то вроде

if (condition1) {
        func1();
} else if (condition2)
        func2();
} else if (condition3)
        func3();
} else {
        func4();
}

, это, скорее всего, относительно намного медленнее, чем просто использование одного указателя на функцию. Хотя вызов функции через указатель имеет некоторые (обычно пренебрежимые) накладные расходы, обычно для сравнения актуальна разница между прямым вызовом функции и вызовом через указатель.

И, во-вторых, никогда не оптимизируйте производительность без каких-либо измерений. Знать, где находятся узкие места, очень сложно (читать невозможно ), и иногда это может быть не интуитивно понятно (например, разработчики ядра Linux начали удалять ключевое слово inline из функций потому что это действительно сказывалось на производительности).

9
ответ дан 7 November 2019 в 09:05
поделиться

Многие люди дали хорошие ответы, но я все еще думаю, что кое-что упускается. Указатели функций добавляют дополнительное разыменование, которое делает их на несколько циклов медленнее, это число может увеличиваться из-за плохого предсказания ветвления (что, кстати, почти не имеет ничего общего с самим указателем функции). Кроме того, функции, вызываемые через указатель, не могут быть встроены. Но людям не хватает того, что большинство людей используют указатели на функции для оптимизации.

Чаще всего указатели на функции в API c / c ++ встречаются в виде функций обратного вызова. Причина, по которой так много API-интерфейсов делают это, заключается в том, что написание системы, которая вызывает указатель функции всякий раз, когда происходят события, намного эффективнее, чем другие методы, такие как передача сообщений. Лично я также использовал указатели на функции как часть более сложной системы обработки ввода, где каждой клавише на клавиатуре назначен указатель функции через таблицу переходов. Это позволило мне удалить любое ветвление или логику из системы ввода и просто обрабатывать входящие нажатия клавиш.

9
ответ дан 7 November 2019 в 09:05
поделиться

В предыдущих ответах много хороших замечаний.

Однако взгляните на функцию сравнения C qsort. Поскольку функция сравнения не может быть встроена и должна соответствовать стандартным соглашениям о вызовах на основе стека, общее время выполнения сортировки может быть на порядок (точнее, в 3-10 раз) медленнее для целочисленных ключей, чем в противном случае тот же код с прямым встроенным вызовом.

Типичным встроенным сравнением может быть последовательность простых инструкций CMP и, возможно, CMOV / SET. Вызов функции также влечет за собой накладные расходы на CALL, настройку кадра стека, выполнение сравнения, разрыв кадра стека и возврат результата. Обратите внимание, что операции со стеком могут вызывать остановку конвейера из-за длины конвейера ЦП и виртуальных регистров. Например, если значение, скажем, eax необходимо до того, как последняя измененная команда eax завершит выполнение (что обычно занимает около 12 тактов на новейших процессорах). Если ЦП не сможет выполнить другие инструкции, не дожидаясь этого, произойдет остановка конвейера.

7
ответ дан 7 November 2019 в 09:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: