Когда использовать встроенную функцию, а когда нет?


185

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

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


11
inlineдля новичка в C ++ то CFLAGSже, что для новичка в Gentoo: нет, компиляция с ним -O3 -funroll-loops -finline-functionsне заставит ваш старый Pentium летать;)
Грегори Пакош

1
Причина, по которой не используется inline, заключается в том, что некоторые отладчики не позволяют вам установить точку останова или перейти к встроенной функции.
Роб ДеФрисс


5
Вы не должны определять, должна ли функция быть встроенной или нет. Пусть компилятор сделает это; это лучше, чем вы (и может встроенные функции выборочно в зависимости от среды каждого вызова).
Дэвид Торнли

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

Ответы:


210

Избежать затрат на вызов функции - это только половина дела.

делать:

  • использовать inlineвместо#define
  • очень маленькие функции являются хорошими кандидатами для inline: более быстрого кода и меньших исполняемых файлов (больше шансов остаться в кеше кода)
  • функция маленькая и вызывается очень часто

нет:

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

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

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

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

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

Ссылки:


РЕДАКТИРОВАТЬ: Бьярне Страуструп, Язык программирования C ++:

Функция может быть определена как inline. Например:

inline int fac(int n)
{
  return (n < 2) ? 1 : n * fac(n-1);
}

Спецификатор inlineявляется подсказкой для компилятора, что он должен попытаться сгенерировать код для вызова fac()inline, а не записывать код для функции один раз, а затем вызывать через обычный механизм вызова функции. Умный компилятор может сгенерировать константу 720для вызова fac(6). Возможность взаимно рекурсивных встроенных функций, встроенных функций, которые повторяются или не зависят от ввода и т. Д., Делает невозможным гарантирование того, что каждый вызов inlineфункции действительно встроен. Степень хитрости компилятора не может быть законодательно установлена, поэтому один компилятор может генерировать 720, другой 6 * fac(5)и еще один необъявленный вызов fac(6).

Чтобы сделать встраивание возможным в отсутствие необычайно умных средств компиляции и компоновки, в область действия должно входить определение, а не просто объявление встроенной функции (§9.2). inlineEspecifier не влияет на семантику функции. В частности, встроенная функция по-прежнему имеет уникальный адрес и staticпеременные (§7.1.2) встроенной функции.

EDIT2: ISO-IEC 14882-1998, 7.1.2 Спецификаторы функций

Объявление функции (8.3.5, 9.3, 11.4) со inlineспецификатором объявляет встроенную функцию. Встроенный спецификатор указывает реализации, что внутренняя замена тела функции в точке вызова должна быть предпочтительнее обычного механизма вызова функции. Реализация не требуется для выполнения этой внутренней замены в точке вызова; однако, даже если эта встроенная замена не указана, другие правила для встроенных функций, определенные в 7.1.2, все равно должны соблюдаться.


34
inlineэто гораздо больше, чем подсказка компилятору. Это изменяет языковые правила о нескольких определениях. Кроме того, наличие статических данных не является чугунной причиной, чтобы избежать встраивания функции. Реализация обязана выделять один статический объект для каждой статической функции независимо от того, объявлена ​​функция inlineили нет. Классы все еще расширяемы, если у них есть встроенные конструкторы и виртуальные деструкторы. И пустой деструктор фигурной скобки - это единственная виртуальная функция, которую иногда стоит оставить в строке.
CB Bailey

2
Это подсказка в том смысле, что функция не обязательно должна быть встроенной (но английский не мой родной язык). Что касается статики в отмеченных функциях inline, то результат заключается в том, что функция не становится встроенной: вы платите цену за вызов, а также каждая единица перевода, которая включает и вызывает функцию, получает собственную копию кода и статических переменных. Причиной отказа от использования конструкторов и деструкторов при разработке библиотеки является двоичная совместимость с будущими версиями вашей библиотеки
Грегори Пакош

14
Неверно называть это «подсказкой компилятору». На самом деле, не- inlineфункции могут быть встроены, если компилятор чувствует себя так. И inlineфункции не будут встроены, если компилятор решит не включать их. Как сказал Чарльз Бейли, это меняет языковые правила. Вместо того, чтобы думать об этом как о подсказке по оптимизации, более правильно думать об этом как о совершенно другой концепции. inlineКлючевое слово указывает компилятору разрешить несколько определений, и больше ничего. Оптимизация «встраивания» может применяться практически к любой функции, независимо от того, помечена она или нет inline.
Джалф

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

2
@GregoryPakosz: Но мы не все используем inlineдля того, чтобы получить встраивание функций. Иногда нам нужны другие преимущества, такие как обход ODR.
Гонки легкости на орбите

57

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

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

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


У меня такое ощущение, что это скорее счастливое совпадение, чем преднамеренная особенность C ++. Идея очень похожа на «статические» глобальные переменные из C. Хотя это очень интересный ответ. Я хотел бы, чтобы они просто использовали ключевое слово как «внутреннее» для обозначения внутренней связи.
Рено Линдек

+1. @ Rehno: я не совсем уверен, что вы говорите. Какое отношение имеет связь с inlineключевым словом? И что такое счастливое совпадение?
Джалф

@jalf: Читая мой комментарий в ретроспективе, я понимаю, что он довольно расплывчатый и не очень хорошо продуманный. Определение одной и той же функции в нескольких файлах приводит к ошибке компоновщика, которую можно устранить, объявив функцию «статическая». Тем не менее, «встроенный» позволяет вам делать то же самое с тонкими различиями, что они фактически не получают внутренней связи, как «статический». Я подозреваю, что на самом деле это скорее совпадение, потому что разработчики / разработчики языка поняли, что им нужно будет сделать что-то особенное с функциями, объявленными в заголовочных файлах, и это перенесено в 'inline'.
Рено Линдек

4
Не уверен, почему ваш комментарий набрал так много голосов, так как производительность является основной причиной для использования встроенного.
gast128

10

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

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

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


Действительно, у меня есть подозрения, что некоторые компиляторы очень хитро игнорируют «inline» полностью и отвечают только на «__inline» или «__force_inline». Я полагаю, это должно предотвратить злоупотребления!
Рено Линдек

Обычно это не так. inline - это только подсказка, но подсказка, которую большинство компиляторов воспринимают всерьез. Вы можете настроить компилятор на испускание языка ассемблера вместе с объектным кодом ( /FAcsв Visual Studio, -sв GCC), чтобы увидеть, что именно он делает. По моему опыту, оба этих компилятора довольно сильно взвешивают встроенное ключевое слово.
Crashworks

1
Это интересно, потому что по моему опыту ни g ++, ни VC inlineвообще не взвешивают ключевые слова. То есть, если вы видите встроенную функцию и удаляете inlineиз нее спецификатор, она все равно будет встроенной. Если у вас есть конкретные примеры обратного, поделитесь ими!
Павел Минаев

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

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

5

Лучший способ выяснить это - профилировать вашу программу и пометить небольшие функции, которые вызываются много раз и прожигать циклы ЦП inline. Ключевое слово здесь - «маленький» - как только издержки вызова функции незначительны по сравнению с временем, потраченным на функцию, бессмысленно их включать.

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


4

Преждевременная оптимизация - корень всего зла!

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

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

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


2

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

Вы можете прочитать больше о inline в faq c ++


1

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

void IncreaseCount() { freeInstancesCnt++; }

Читатель сразу знает полную семантику кода.


0

Я обычно следую правилу большого пальца, где я делаю функцию с 3-4 простыми утверждениями как встроенными. Но хорошо помнить, что это всего лишь подсказка компилятору. Последний вызов, чтобы сделать его встроенным или нет, принимается только компилятором. Если их будет больше, чем много, я не буду объявлять их как встроенные, так как с тупым компилятором это может привести к раздуванию кода.


0

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


0

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

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

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

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

0

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

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


1
Это другая проблема. Вы получаете проблему перестроения для кода, который помещен в заголовочный файл. Независимо от того, помечено оно inlineили нет, не имеет значения (кроме как без inlineключевого слова, вы получите ошибки компоновщика - но inlineключевое слово не является проблемой, вызывающей чрезмерное перестроение.
jalf

Однако изменение встроенного метода вызовет чрезмерную сборку по сравнению с изменением не встроенного метода в файле источника.
Томас Мэтьюз

0

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


0

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


-2

Я прочитал некоторые ответы и вижу, что некоторые вещи отсутствуют.

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

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

Я знаю что inline это подсказка или запрос к компилятору

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

Так когда же использовать inline?

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

Например, у меня есть эта функция:

inline bool ValidUser(const std::string& username, const std::string& password)
{
    //here it is quite long function
}

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


3
в строке все еще подсказка. Компилятор может не встроиться, если сочтет, что ваша функция слишком раздута.
Пит

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

@ user2918461 Я поддерживаю встроенное утверждение, это просто подсказка. Это было поддержано многими сайтами и книгами
WARhead
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.