Что не так с использованием встроенных функций?
Хотя в некоторых ситуациях было бы очень удобно использовать встроенные функции,
Есть ли недостатки с встроенными функциями?
Вывод:
Видимо, нет ничего плохого в использовании встроенных функций.
Но стоит отметить следующие моменты!
Чрезмерное использование встраивания может замедлить работу программ. В зависимости от размера функции, ее вставка может привести к увеличению или уменьшению размера кода. Встраивание очень маленькой функции доступа обычно уменьшает размер кода, тогда как встраивание очень большой функции может значительно увеличить размер кода. На современных процессорах меньший код обычно выполняется быстрее из-за лучшего использования кэша команд. - Google Guidelines
Преимущества скорости встроенных функций имеют тенденцию уменьшаться по мере увеличения размера. В какой-то момент накладные расходы на вызов функции становятся небольшими по сравнению с выполнением тела функции, и выгода теряется - источник
Есть несколько ситуаций, когда встроенная функция может не работать:
- Для функции, возвращающей значения; если существует оператор возврата
- Для функции, не возвращающей никаких значений; если существует оператор loop, switch или goto.
- Если функция рекурсивная. -Источник
__inline
ключевое слово заставляет функцию быть встроенной, только если вы укажете опцию оптимизации. Если указана оптимизация, или нет__inline
Соблюдается, зависит от настройки опции встроенного оптимизатора. По умолчанию встроенный параметр действует при каждом запуске оптимизатора. Если вы укажете оптимизировать, вы также должны указать опцию noinline, если вы хотите__inline
ключевое слово, которое нужно игнорировать. -Источник
13 ответов
Стоит отметить, что ключевое слово inline на самом деле является лишь подсказкой для компилятора. Компилятор может игнорировать встроенный код и просто генерировать код для функции где-нибудь.
Основным недостатком встроенных функций является то, что они могут увеличить размер исполняемого файла (в зависимости от количества экземпляров). Это может быть проблемой на некоторых платформах (например, встроенных системах), особенно если сама функция рекурсивна.
Я также рекомендовал бы сделать встроенные функции очень маленькими - преимущества встроенных функций в скорости, как правило, уменьшаются с ростом размера функции. В какой-то момент накладные расходы на вызов функции становятся небольшими по сравнению с выполнением тела функции, и выгода теряется.
Это может увеличить размер исполняемого файла, и я не думаю, что компиляторы всегда будут делать их встроенными, даже если вы использовали ключевое слово inline. (Или наоборот, как сказал Вайбхав?...)
Я думаю, что это нормально, если функция имеет только 1 или 2 оператора.
Редактировать: Вот что говорит об этом документ Linux CodingStyle:
Глава 15: Встроенная болезнь
Похоже, что существует распространенное заблуждение, что у gcc есть волшебная опция ускорения "сделай меня быстрее", которая называется "встроенная". Хотя использование встроенных строк может быть целесообразным (например, в качестве средства замены макросов, см. Главу 12), это очень часто не так. Широкое использование встроенного ключевого слова приводит к гораздо большему ядру, что, в свою очередь, замедляет работу системы в целом, из-за большей нагрузки на процессор для кэш-памяти и просто потому, что для кэша страниц доступно меньше памяти. Просто подумай об этом; Пропуск кэша страницы вызывает поиск диска, который легко занимает 5 миллисекунд. Есть много циклов процессора, которые могут идти в эти 5 миллисекунд.
Разумное правило заключается в том, чтобы не вставлять в строку функции, содержащие более 3 строк кода. Исключением из этого правила являются случаи, когда параметр, как известно, является константой времени компиляции, и в результате этой константы вы знаете, что компилятор сможет оптимизировать большую часть вашей функции во время компиляции. Хороший пример этого более позднего случая, см. Встроенную функцию kmalloc().
Часто люди утверждают, что добавление встроенных функций к статическим функциям, которые используются только один раз, всегда является выигрышем, так как нет компромисса в пространстве. Хотя это технически правильно, gcc может автоматически встроить их без помощи, а проблема с обслуживанием, заключающаяся в удалении строки, когда появляется второй пользователь, перевешивает потенциальное значение подсказки, которая говорит gcc сделать что-то, что он сделал бы в любом случае.
Существует проблема с inline - после того, как вы определили функцию в заголовочном файле (который подразумевает inline, явный или неявный, определяя тело функции-члена внутри класса), не существует простого способа изменить его, не заставляя пользователей перекомпилировать (в отличие от перекомпоновки). Часто это вызывает проблемы, особенно если рассматриваемая функция определена в библиотеке, а заголовок является частью ее интерфейса.
Как уже упоминалось, ключевое слово inline является лишь подсказкой для компилятора. На самом деле, большинство современных компиляторов будут полностью игнорировать эту подсказку. Компилятор имеет свою собственную эвристику, чтобы решить, следует ли встроить функцию, и, откровенно говоря, не хочет вашего совета, большое спасибо.
Если вы действительно, действительно хотите сделать что-то встроенное, если вы действительно его профилировали и посмотрели на разборку, чтобы убедиться, что переопределение эвристики компилятора действительно имеет смысл, то это возможно:
- В VC++ используйте ключевое слово __forceinline
- В GCC используйте __attribute__((always_inline))
Ключевое слово inline имеет и второе, правильное назначение - объявление функций в заголовочных файлах, но не внутри определения класса. Ключевое слово inline необходимо, чтобы компилятор не генерировал несколько определений функции.
Я согласен с другими постами:
- inline может быть лишним, потому что компилятор сделает это
- встроенный может раздуть ваш код
Третий момент - это может заставить вас раскрыть детали реализации в ваших заголовках, например.
class OtherObject;
class Object {
public:
void someFunc(OtherObject& otherObj) {
otherObj.doIt(); // Yikes requires OtherObj declaration!
}
};
Без встроенного предварительного объявления OtherObject было все, что вам нужно. Со встроенным заголовком ваш заголовок нуждается в определении OtherObject.
Я не знаю, связан ли мой ответ с вопросом, но:
Будьте очень осторожны с встроенными виртуальными методами! Некоторые ошибочные компиляторы (например, в предыдущих версиях Visual C++) генерировали встроенный код для виртуальных методов, где стандартным поведением было ничего не делать, кроме как переходить по дереву наследования и вызывать соответствующий метод.
Я сомневаюсь. Даже компилятор автоматически добавляет некоторые функции для оптимизации.
Включение больших функций может сделать программу больше, что приведет к большему количеству промахов в кеше и замедлению работы.
Решить, когда функция достаточно мала, чтобы встраивание увеличило производительность, довольно сложно. Google C++ Style Guide рекомендует использовать только встроенные функции из 10 строк или менее.
Следует также отметить, что встроенное ключевое слово является только запросом. Компилятор может решить не вставлять его, аналогично, компилятор может сделать встроенную функцию, которую вы не определили как встроенную, если он считает, что компромисс между скоростью и размером того стоит.
Это решение обычно принимается на основе ряда вещей, таких как установка между оптимизацией по скорости (избегает вызова функции) и оптимизацией по размеру (встраивание может вызвать раздувание кода, поэтому не подходит для больших многократно используемых функций).
с компилятором VC++ вы можете переопределить это решение, используя __forceinline
ТАК в общем: используйте inline, если вы действительно хотите иметь функцию в заголовке, но в другом месте нет особого смысла, потому что если вы собираетесь что-то извлечь из этого, хороший компилятор все равно сделает ее встроенной для вас.
Чрезмерное встраивание функций может увеличить размер скомпилированного исполняемого файла, что может отрицательно сказаться на производительности кэша, но в настоящее время компилятор сам принимает решение о встраивании функции (в зависимости от многих критериев) и игнорирует встроенное ключевое слово.
Среди других проблем со встроенными функциями, которые я видел слишком часто (я видел встроенные функции из 500 строк), следует помнить следующее:
построить нестабильность
- Изменение источника встроенной функции приводит к перекомпиляции всех пользователей заголовка.
#include
S утечка в клиента. Это может быть очень неприятно, если вы переделываете встроенную функцию и удаляете неиспользуемый заголовок, на который полагался какой-либо клиент.
размер исполняемого файла
- Каждый раз, когда вместо инструкции вызова указывается inline, компилятор должен генерировать весь код inline. Это нормально, если код функции короткий (одна или две строки), не очень хорошо, если функция длинная
- Некоторые функции могут генерировать намного больше кода, чем кажется на первый взгляд. В данном случае речь идет о "тривиальном" деструкторе класса, который имеет много переменных, не являющихся членами модуля (или двух или трех переменных-членов с довольно грязными деструкторами). Вызов должен быть сгенерирован для каждого деструктора.
время исполнения
- это очень зависит от вашего кеша процессора и разделяемых библиотек, но локальность ссылки важна. Если код, который вы, возможно, вставляете, хранится в кэше процессора в одном месте, ряд клиентов может найти код, не страдающий от пропадания кэша и последующего извлечения памяти (и, что еще хуже, в случае извлечения диска), К сожалению, это один из тех случаев, когда вам действительно нужно провести анализ производительности.
Стандарт кодирования, в котором я работаю, ограничивает встроенные функции простыми установщиками / получателями и, в частности, говорит, что деструкторы не должны быть встроенными, если только у вас нет измерений производительности, чтобы показать, что встраивание дает заметное преимущество.
В дополнение к другим замечательным ответам, по крайней мере, однажды я видел случай, когда принудительное встраивание фактически замедлило затронутый код в 1,5 раза. Внутри был вложенный цикл (довольно маленький), и когда эта функция была скомпилирована как отдельная единица, компилятору удалось ее эффективно развернуть и оптимизировать. Но когда та же функция была встроена в гораздо большую внешнюю функцию, компилятор (MSVC 2017) не смог оптимизировать этот цикл.
Как говорили другие люди, встроенная функция может создать проблему, если код большой. Поскольку каждая инструкция хранится в определенном месте памяти, перегрузка встроенной функции заставляет код занимать больше времени для выполнения.
Есть несколько других ситуаций, когда inline может не работать
- не работает в случае рекурсивной функции.
- Также может не работать со статической переменной.
- это также не работает в случае использования цикла, переключателя и т. д. или мы можем сказать, что с несколькими операторами.
- И функция main не может работать как встроенная функция.