Когда «встроенный» неэффективен? (в С) - PullRequest
15 голосов
/ 24 февраля 2009

Некоторые люди любят использовать ключевое слово inline в C и помещать большие функции в заголовки . Когда вы считаете это неэффективным? Я считаю, что это иногда даже раздражает, потому что это необычно.

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

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

Спасибо за ваши ответы!

Ответы [ 12 ]

26 голосов
/ 24 февраля 2009

inline делает две вещи:

  1. дает вам освобождение от "правила одного определения" (см. Ниже). Это всегда применяется.
  2. Дает компилятору подсказку, чтобы избежать вызова функции. Компилятор может игнорировать это.

# 1 Может быть очень полезным (например, поместить определение в заголовок, если оно короткое), даже если # 2 отключено.

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


[РЕДАКТИРОВАТЬ: Полный список литературы и соответствующий текст]

Обе вышеуказанные точки следуют из стандарта ISO / ANSI (ISO / IEC 9899: 1999 (E), широко известного как «C99»).

В §6.9 «Внешнее определение», параграф 5:

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

Хотя эквивалентное определение в C ++ явно названо правилом единого определения (ODR), оно служит той же цели. Внешние элементы (т. Е. Не «статические» и, следовательно, локальные по отношению к одному модулю трансляции - как правило, к одному исходному файлу) могут быть определены только один раз , если не является функцией и inline.

В §6.7.4, «Спецификаторы функций», определено ключевое слово inline:

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

И сноска (ненормативная), но дает пояснения:

Используя, например, альтернативу обычному механизму вызова функции, например, "inline substitution". Встроенная замена не является текстовой заменой и не создает новую функцию. Поэтому, например, при расширении макроса, используемого в теле функции, используется определение, которое оно имело в точке появления тела функции, а не там, где вызывается функция; и идентификаторы ссылаются на объявления в области видимости тела. Аналогично, функция имеет один адрес, независимо от количества встроенных определений, которые встречаются в дополнение к внешнему определению.

Резюме: большинство пользователей C и C ++ ожидают от inline не того, что они получают. Его очевидная основная цель, чтобы избежать функциональных вызовов, полностью необязательна. Но для обеспечения возможности отдельной компиляции требуется ослабление одного определения.

(все выделено в кавычках из стандарта.)


РЕДАКТИРОВАТЬ 2: Несколько примечаний:

  • Существуют различные ограничения для внешних встроенных функций. У вас не может быть статической переменной в функции, и вы не можете ссылаться на статические объекты / функции области видимости TU.
  • Только что видел это на VC ++ " оптимизация всей программы ", который является примером компилятора, делающего свою собственную встроенную вещь, а не автора.
5 голосов
/ 25 февраля 2009

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

Конечно, этот аргумент применяется, только если функция является частью вашего публичного API.

5 голосов
/ 25 февраля 2009

Пример, иллюстрирующий преимущества inline. sinCos.h:

int16 sinLUT[ TWO_PI ]; 

static inline int16_t cos_LUT( int16_t x ) {
    return sin_LUT( x + PI_OVER_TWO )
}

static inline int16_t sin_LUT( int16_t x ) {
    return sinLUT[(uint16_t)x];
}

Когда вы выполняете какие-то сложные вычисления и хотите избежать ненужных циклов при вычислении sin / cos, вы заменяете sin / cos на LUT.

Когда вы компилируете без inline, компилятор не будет оптимизировать цикл , а выходной файл .asm будет показывать что-то вроде:

;*----------------------------------------------------------------------------*
;*   SOFTWARE PIPELINE INFORMATION
;*      Disqualified loop: Loop contains a call
;*----------------------------------------------------------------------------*

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

Выходной файл .asm будет иметь оптимизированный «конвейерный» цикл (т. Е. Он будет пытаться полностью использовать все ALU процессора и будет поддерживать конвейер процессора заполненным без NOPS).


В этом конкретном случае я смог увеличить производительность примерно в 2 или 4 раза, что позволило мне достичь того, что мне было нужно, в течение срока, установленного в реальном времени.


p.s. Я работал на процессоре с фиксированной запятой ... и любые операции с плавающей запятой, такие как sin / cos, убивали мою производительность.

5 голосов
/ 24 февраля 2009

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

4 голосов
/ 24 февраля 2009

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

3 голосов
/ 24 февраля 2009

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

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

2 голосов
/ 24 февраля 2009

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

Это еще один случай, о котором предупреждал Кнут о преждевременной оптимизации.

2 голосов
/ 24 февраля 2009

Я в основном использую встроенные функции как макросы безопасного типа. Говорили о добавлении поддержки оптимизации времени компоновки в GCC в течение достаточно долгого времени, особенно после появления LLVM. Я пока не знаю, сколько из этого было на самом деле реализовано.

2 голосов
/ 24 февраля 2009

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

2 голосов
/ 24 февраля 2009

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

...