Преимущества встроенных функций в C ++?


254

Каковы преимущества / недостатки использования встроенных функций в C ++? Я вижу, что это только увеличивает производительность для кода, который выводит компилятор, но с современными оптимизированными компиляторами, быстрыми процессорами, огромной памятью и т. Д. (Не так, как в 1980 году <где памяти было мало, и все должно было уместиться в 100 КБ памяти), что преимущества они действительно имеют сегодня?


48
Это один из тех вопросов, где общеизвестно, что это неправильно. Все ответили стандартным ответом Comp Sci. (Встраивание экономит затраты на вызов функции, но увеличивает размер кода). Мусор. Он предоставляет простой механизм компилятору для применения большего количества ОПТИМИЗАЦИЙ.
Мартин Йорк,

37
Это один из тех ответов, выдаваемых за комментарии. Если вам не нравится ни один из ответов, которые были опубликованы, опубликуйте свой собственный ответ и посмотрите, как он работает.
Дэйв Ван ден Эйнде

10
Основа этого вопроса ошибочна. Встроенные функции C ++ имеют мало общего с компилятором, встроенным во время компиляции. К сожалению, inlineэто ключевое слово c ++, а встраивание - это метод оптимизации компилятора. Посмотрите этот вопрос « когда я должен написать ключевое слово inlineдля функции / метода » для правильного ответа.
deft_code

3
@JoseVega Ваша ссылка искажена - текущая ссылка exforsys.com/tutorials/c-plus-plus/inline-functions.html

Ответы:


143

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

Имеет ли это существенное значение? Не заметно на современном оборудовании для большинства. Но это может иметь значение, которого достаточно для некоторых людей.

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

Я мог видеть ситуацию, подобную этой, имеющую заметную разницу:

inline int aplusb_pow2(int a, int b) {
  return (a + b)*(a + b) ;
}

for(int a = 0; a < 900000; ++a)
    for(int b = 0; b < 900000; ++b)
        aplusb_pow2(a, b);

26
Как я подозревал, встраивание не имеет значения для вышеизложенного. Скомпилировано с gcc 4.01. Версия 1 заставила использовать встраивание: 48.318u 1.042s 5: 51.39 99.4% 0 + 0k 0 + 0io 0pf + 0w Версия 2 принудительно не использовала встраивание 348.311u 1.019s 5: 52.31 99.1% 0 + 0k 0 + 0io 0pf + 0w Это хороший пример, когда общеизвестно, что это неправильно.
Мартин Йорк,

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

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

4
Причиной отсутствия различий может быть то, что компилятор может быть встроен сам по себе; или что код небольшой, поэтому нет проблем с предварительной выборкой кода.
einpoklum

3
@einpoklum Компилятор, возможно, даже оптимизировал весь цикл из-за этого.
noɥʇʎԀʎzɐɹƆ

197

преимущества

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

Недостатки

  • Это может сделать ваш код больше (например, если вы используете inline для нетривиальных функций). Таким образом, это может спровоцировать оптимизацию подкачки и компиляции.
  • Это немного нарушает вашу инкапсуляцию, потому что она раскрывает внутреннюю часть обработки вашего объекта (но тогда каждый «закрытый» член тоже будет). Это означает, что вы не должны использовать встраивание в шаблоне PImpl.
  • Это немного нарушает инкапсуляцию 2: встраивание C ++ разрешается во время компиляции. Это означает, что если вы измените код встроенной функции, вам нужно будет перекомпилировать весь код, используя его, чтобы быть уверенным, что он будет обновлен (по той же причине я избегаю значений по умолчанию для параметров функции)
  • При использовании в заголовке он увеличивает размер вашего заголовочного файла и, таким образом, разбавляет интересную информацию (например, список методов класса) кодом, который не волнует пользователя (поэтому я объявляю встроенные функции внутри класс, но определит его в заголовке после тела класса, а не внутри тела класса).

Магия

  • Компилятор может включать или не включать функции, которые вы пометили как встроенные; он также может решить встроить функции, не помеченные как встроенные, во время компиляции или компоновки.
  • Inline работает как копирование / вставка, управляемая компилятором, что сильно отличается от макроса препроцессора: макрос будет принудительно встроен, будет загрязнять все пространства имен и код, не будет легко отлаживаться и будет выполняться даже если бы компилятор считал это неэффективным.
  • Каждый метод класса, определенный внутри тела самого класса, считается «встроенным» (даже если компилятор все еще может не встроить его в класс).
  • Виртуальные методы не должны быть встроенными. Тем не менее, иногда, когда компилятор может точно знать тип объекта (т. Е. Объект был объявлен и создан внутри одного и того же тела функции), даже виртуальная функция будет встроена, потому что компилятор точно знает тип объекта.
  • Методы / функции шаблона не всегда встроены (их присутствие в заголовке не сделает их автоматически встроенными).
  • Следующим шагом после «inline» является метапрограммирование шаблона. Т.е., "вставляя" ваш код во время компиляции, иногда компилятор может определить конечный результат функции ... Таким образом, сложный алгоритм иногда может быть сведен к некоему return 42 ;утверждению. Это для меня экстремальное встраивание . Это случается редко в реальной жизни, это увеличивает время компиляции, не увеличивает ваш код и делает ваш код быстрее. Но, как и Грааль, не пытайтесь применять его везде, потому что большая часть обработки не может быть решена таким образом ... Тем не менее, это круто в любом случае ...
    :-p

Вы сказали, что это немного нарушает вашу инкапсуляцию. Не могли бы вы объяснить это на примере?
Разрушитель

6
@PravasiMeet: это C ++. Допустим, вы поставляете DLL / разделяемую библиотеку клиенту, который компилирует его. Встроенная функция foo, использующая переменную-член X и выполняющую работу Y, будет встроена в код клиента. Скажем, нужно доставить обновленную версию вашей DLL, в которой вы изменили переменную-член на Z, и добавить рабочий YY в дополнение к рабочему Y. Клиент просто копирует DLL в свой проект и BOOM, потому что код foo в их двоичном коде нет обновленного кода, который вы написали ... Несмотря на то, что клиент не имеет легального доступа к вашему личному коду, встраивание делает его довольно «публичным».
paercebal

@paercebal Что касается вашего второго до последнего пункта, можете ли вы привести пример, когда шаблон функции не встроен? Я думал, что они всегда были встроены, хотя сейчас у меня нет под рукой справки (хотя простой тест подтверждает это).
Конрад Рудольф

@KonradRudolph В n4594 я вижу: 3.2/6: There can be more than one definition of [..] inline function with external linkage [..] non-static function template. На 5.1.5 / 6 For a generic lambda, the closure type has a public inline function call operator member template. И 7.1.2/2: the use of inline keyword is to declare an inline functionтам, где есть предложение встроить тело функции в точке вызова. Итак, я прихожу к выводу, что даже если они могут вести себя одинаково, встроенные функции и шаблоны функций по-прежнему являются отдельными, ортогональные понятия, которые можно смешивать (т.
Е.

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

42

В архаичных C и C ++ inlineэто похоже на register: предложение (не более, чем предложение) компилятору о возможной оптимизации.

В современном C ++ inlineсообщает компоновщику, что, если несколько определений (не объявлений) находятся в разных единицах перевода, они все одинаковы, и компоновщик может свободно хранить одно и отбрасывать все остальные.

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

Функции-члены, определенные внутри класса, по умолчанию являются «встроенными», как и шаблонные функции (в отличие от глобальных функций).

//fileA.h
inline void afunc()
{ std::cout << "this is afunc" << std::endl; }

//file1.cpp
#include "fileA.h"
void acall()
{ afunc(); }

//main.cpp
#include "fileA.h"
void acall();

int main()
{ 
   afunc(); 
   acall();
}

//output
this is afunc
this is afunc

Обратите внимание на включение файла file..h в два файла .cpp, в результате чего получается два экземпляра afunc(). Линкер откажется от одного из них. Если нет inline, компоновщик будет жаловаться.


16

Встраивание - это предложение для компилятора, которое он может игнорировать. Это идеально подходит для небольших кусков кода.

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

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

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

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


12

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

Преимущества: -

  1. Не требует накладных расходов на вызов функции.

  2. Это также экономит накладные расходы на переменные push / pop в стеке при вызове функции.

  3. Это также экономит накладные расходы на обратный вызов из функции.

  4. Это увеличивает местность ссылки, используя кэш инструкций.

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

Чтобы узнать больше об этом можно перейти по этой ссылке http://tajendrasengar.blogspot.com/2010/03/what-is-inline-function-in-cc.html


4
1) Это предложение, а не инструкция. 2) Это может привести к большему количеству ошибок в кэше из-за увеличения размера кода, если часто используется часто используемая функция
Flexo

3
Ссылка в конце - ваш личный блог? Если это так, вы должны объявить это как таковой, иначе это выглядит как спам.
Flexo

6

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

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


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

4

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


Многие компиляторы также не будут этого делать, например, MSVC не сделает этого, если вы не скажете это
paulm

4

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


3

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

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

Когда встроить и / или определить макрос для принудительного включения? - Только тогда, когда у вас есть доказанное и необходимое доказанное увеличение скорости для критической части кода, которая, как известно, влияет на общую производительность приложения.


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

3

Это не все о производительности. Оба C ++ и C используются для встроенного программирования, сидя на вершине аппаратного обеспечения. Если вы, например, хотите написать обработчик прерываний, вам необходимо убедиться, что код может быть выполнен сразу, без замены дополнительных регистров и / или страниц памяти. Вот когда полезен inline. Хорошие компиляторы делают некоторые «вставки» сами, когда нужна скорость, но «встроенные» заставляют их.


1

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


1

Почему бы не сделать все функции встроенными по умолчанию? Потому что это инженерный компромисс. Существует как минимум два типа «оптимизации»: ускорение программы и уменьшение размера (объема памяти) программы. Встраивание вообще ускоряет вещи. Это избавляет от накладных расходов на вызов функции, избегая выталкивания и извлечения параметров из стека. Однако это также увеличивает объем памяти программы, поскольку каждый вызов функции теперь должен быть заменен полным кодом функции. Чтобы сделать вещи еще более сложными, помните, что ЦП хранит часто используемые куски памяти в кеше ЦП для сверхбыстрого доступа. Если вы сделаете образ памяти программы достаточно большим, ваша программа не сможет эффективно использовать кеш, а в худшем случае встраивание может замедлить вашу программу.


1

Наш профессор информатики призвал нас никогда не использовать inline в программе на c ++. Когда его спросили, почему, он любезно объяснил нам, что современные компиляторы должны определять, когда использовать inline автоматически.

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


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

-1

Вывод из другого обсуждения здесь:

Есть ли недостатки с встроенными функциями?

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

Но стоит отметить следующие моменты!

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

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

  • Есть несколько ситуаций, когда встроенная функция может не работать:

    • Для функции, возвращающей значения; если существует оператор возврата
    • Для функции, не возвращающей никаких значений; если существует оператор loop, switch или goto.
    • Если функция рекурсивная. -Источник
  • __inlineКлючевое слово вызывает функцию , которая будет встраиваемой только при указании параметра оптимизировать. Если указан __inlineпараметр «Оптимизировать», зависит от того, соблюдается или нет, параметр настройки встроенного оптимизатора. По умолчанию встроенный параметр действует при каждом запуске оптимизатора. Если вы укажете «Оптимизировать», вы также должны указать опцию noinline, если хотите, чтобы __inlineключевое слово игнорировалось. -Источник


3
Было бы верно, если бы inline был командой, а не подсказкой компилятору. Компилятор на самом деле решает, что встроить.
Мартин Йорк,

1
@LokiAstari Я знаю, что inline - это запрос к компилятору. Мой аргумент: если это подсказка компилятору, мы должны оставить это компилятору, чтобы решить, что лучше. Зачем использовать inline каким-либо образом, даже если вы используете inline, и все же окончательное решение будет принимать компилятор. Мне также интересно, что моя Microsoft представила _forceinline.
Кришна Оза

@krish_oza: Мой комментарий здесь. Это об этом ответе. Ответ здесь совершенно неверный. Поскольку компилятор игнорирует inlineключевое слово для определения того, стоит ли встроенный код, все приведенные выше пункты неверны. Они были бы верны, если бы компилятор использовал ключевое слово для встраивания (оно используется только для определения маркировки нескольких определений для целей связывания).
Мартин Йорк,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.