Когда “встроенный” неэффективен?(в C)

StackOverflow https://stackoverflow.com/questions/582919

  •  06-09-2019
  •  | 
  •  

Вопрос

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

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

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

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

Это было полезно?

Решение

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

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

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

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


[ПРАВИТЬ:Полные ссылки и соответствующий текст]

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

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

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

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

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

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

И сноска (ненормативная), но дающая разъяснение:

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

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

(Все подчеркивания в цитатах из стандарта.)


ПРАВКА 2:Несколько заметок:

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

Другие советы

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

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

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

Пример, иллюстрирующий преимущества inline.синКос.ч :

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.

Когда вы компилируете без встроенного компилятора не будет оптимизировать цикл и output .asm покажет что -то вроде :

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

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

output .asm будет иметь оптимизированный "конвейерный" цикл (т. е.он попытается полностью использовать все ALU процессора и сохранить конвейер процессора заполненным без NOPS).


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


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

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

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

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

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

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

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

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

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

  1. inline действует только как подсказка.
  2. Добавлено совсем недавно.So работает только с последними компиляторами, совместимыми со стандартом.

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

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

так что , если:

статический встроенный беззнаковый int foo(const char * bar)

..не улучшает ситуацию по сравнению со static int foo() пришло время пересмотреть ваши оптимизации (и, вероятно, циклы) или поспорить с вашим компилятором.Будьте особенно осторожны, чтобы сначала поспорить с вашим компилятором, а не с людьми, которые его разрабатывают..или вас просто ждет много неприятного чтения, когда вы откроете свой почтовый ящик на следующий день.

Между тем, когда вы делаете что-то (или пытаетесь сделать что-то) встроенным, действительно ли это оправдывает раздувание?Вы действительно хотите, чтобы эта функция была расширена каждый время его названо?Является ли переход таким дорогостоящим?, ваш компилятор обычно корректен 9/10 раз, проверьте промежуточный вывод (или asm-дампы).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top