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

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

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

Ответ 1

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

делать:

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

не

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

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

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

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

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

Литература:


EDIT: Bjarne Stroustrup, язык программирования С++:

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

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

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

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

Ответ 5

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

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

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

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

Ответ 6

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

вы можете больше узнать о ссылках в С++ faq

Ответ 7

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

void IncreaseCount() { freeInstancesCnt++; }

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

Ответ 8

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

Ответ 9

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

Ответ 10

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

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

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

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

Ответ 11

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

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

Ответ 12

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

Ответ 13

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

Ответ 14

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

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

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

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

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

Итак, когда использовать inline?

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

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

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

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