2009-02-24 2 views
15

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

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

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

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

+0

Я подозреваю, что в конце 1980-х годов, как и «регистр», устарел, «inline» устарел уже несколько лет. –

+0

\ o /, die "inline"! – elmarco

+0

Inline определенно не устарел. Вы когда-нибудь читали исходный код для современного ядра? Inline не имеет отношения к людям, которые пишут приложения, но эти люди не должны использовать C в любом случае, и это так же важно для системного программирования, как и когда-либо. – nosatalian

ответ

26

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

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

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

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


[EDIT: полные ссылки и соответствующий текст]

Две точки выше и следует из стандарта ISO/ANSI (ISO/IEC 9899: 1999 (Е), обычно известные как "C99") ,

В §6.9 «Внешнее определение», пункт 5:

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

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

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

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

и сноска (ненормативный), но обеспечивает уточнение:

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

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

(Все внимание в котировках от стандарта.)


EDIT 2: Несколько замечаний:

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

Я даже не думал о №1, но ты прав - очень полезен! Спасибо за совет. – Mike

+0

-1: # 1 не соответствует действительности - при использовании gcc вы также должны добавить 'static'! – Christoph

+1

@ Кристоф: это просто gcc. Просто зарегистрирован в стандартном документе C99. – Richard

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

Добавлено совсем недавно?Я уверен, что встроенный был в течение по крайней мере дюжины лет (с тех пор, как я начал кодирование в C) –

+0

Inline был в C++ в течение длительного времени. В C он был стандартным с C99 (но не так много компиляторов полностью поддерживает C99), но некоторое время был в разных компиляторах как расширение. Таким образом, для программ на С, использование inline может быть немного головной болью переносимости. –

+0

10yrs. Тем не менее, заставить MS внедрить достойный компилятор C. Во всяком случае, это последнее в C-время;) Добавлено в C99. Смотрите: http://en.wikipedia.org/wiki/C99 – dirkgently

4

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

+0

уверен, что в моем проекте это не так. спасибо – elmarco

+0

+1 интересное наблюдение.! –

5

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

+0

, даже если встроенная функция, с которой вы связываетесь, связана с другим объектом .o? – elmarco

+0

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

2

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

+0

Как уже упоминалось ранее, inline используется для _suggest_ для компилятора, что функция должна быть включена без вызова. Нет никакой гарантии, что он действительно это сделает. – Peter

2

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

2

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

3

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

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

+0

Dang. Я обманул ваш ответ. Ну, этот факт сам по себе ясно показывает, что вы придавали большое понимание, так что повышайте. :-) –

+0

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

2

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

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

5

Пример, иллюстрирующий преимущества встроенного. 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 с LUT.

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

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

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

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


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


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

5

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

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

1

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

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

, если:

статических рядный беззнаковый INT Foo (Const символ * бар)

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

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