Что плохого в использовании встроенных функций?

Хотя в некоторых ситуациях было бы очень удобно использовать встроенные функции,

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

Заключение:

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

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

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

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

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

    • For a function returning values; if a return statement exists.
    • Для функции, не возвращающей никаких значений; если существует цикл, переключатель или оператор goto.
    • Если функция рекурсивная. -Источник
  • Ключевое слово __inline заставляет функцию быть встроенной, только если вы укажете опцию оптимизации. Если указано optimize, то, будет ли учитываться __inline, зависит от настройки параметра встроенного оптимизатора. По умолчанию встроенный параметр действует при каждом запуске оптимизатора. Если вы указываете optimize, вы также должны указать опцию noinline, если хотите, чтобы ключевое слово __inline игнорировалось. -Источник


person prakash    schedule 13.09.2008    source источник
comment
Хорошее резюме. Обратите внимание, что ваши маркеры о том, когда они встроены, применяются компилятором, а когда нет, скорее зависят от компилятора. (MSVC будет делать это иначе, чем gcc, иначе, чем ...)   -  person Martin Ba    schedule 04.09.2010
comment
Есть несколько ситуаций, когда встроенная функция может не работать: для функции, возвращающей значения; если существует оператор возврата. Для функции, не возвращающей никаких значений, вы также можете просто сказать, что встроенные функции могут никогда не работать.   -  person Jossie Calderon    schedule 25.06.2016
comment
Ваши очки сосредоточены только на производительности. ИМО, более важным моментом является то, что inline позволяет избежать нарушений ODR для автономных функций, определенных в библиотеках только для заголовков. Это использование inline, которое компилятор не может игнорировать. Он по-прежнему может отказаться от фактического встраивания функции, но должен рассматривать ее так, как будто существует только одно определение.   -  person zett42    schedule 29.09.2018
comment
Дополнительные объяснения по поводу ODR.   -  person zett42    schedule 29.09.2018


Ответы (12)


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

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

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

person Alan    schedule 13.09.2008

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

Я думаю, что обычно нормально, если функция имеет только 1 или 2 оператора.

Изменить: вот что об этом говорится в CodingStyle документе linux :

Глава 15: Внутренняя болезнь

Похоже, существует распространенное заблуждение, что в gcc есть волшебная опция ускорения «сделай меня быстрее», называемая «встроенной». Хотя использование встроенных строк может быть уместным (например, как средство замены макросов, см. Главу 12), очень часто это не так. Обильное использование ключевого слова inline приводит к гораздо большему размеру ядра, что, в свою очередь, замедляет работу системы в целом из-за большего объема памяти icache для ЦП и просто потому, что для кэша страниц доступно меньше памяти. Просто подумай об этом; промах кэша страниц вызывает поиск по диску, который легко занимает 5 миллисекунд. Существует МНОГО циклов процессора, которые могут занимать эти 5 миллисекунд.

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

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

person Paige Ruten    schedule 13.09.2008
comment
В VC ++ он увеличит размер, не уверен в других компиляторах. Но если они все же решат сделать его встроенным, я уверен, что это тоже сделало бы его больше. - person Brian R. Bondy; 14.09.2008
comment
под ними я подразумеваю рассматриваемый компилятор. - person Brian R. Bondy; 14.09.2008
comment
Да, увеличение размера было основным, о котором я слышал ... любопытно узнать, есть ли еще! - person prakash; 14.09.2008
comment
Гораздо более важным, чем длина функции, IMHO, является вопрос о том, позволит ли знание аргументов, передаваемых на различных сайтах вызовов, применять оптимизацию именно к ним. Есть много ситуаций, когда 99% вызовов определенной функции передают константу [не всегда одну и ту же] конкретному аргументу, и когда знание того, что определенный параметр будет постоянным, позволит компилятору опустить более 90% код, связанный с функцией. - person supercat; 27.07.2015
comment
@ BrianR.Bondy: Встраивание крошечных функций, которые получают или устанавливают один член, обычно уменьшает размер двоичного файла. Для установки и создания call требуется больше инструкций, чем для загрузки или сохранения одной вещи, а возможность оптимизации после встраивания часто означает, что что-то может просто храниться в регистре на протяжении всего цикла вместо повторных вызовов. - person Peter Cordes; 29.09.2018

Я согласен с другими сообщениями:

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

Третий момент - это может вынудить вас раскрыть детали реализации в ваших заголовках, например,

class OtherObject;

class Object {
public:
    void someFunc(OtherObject& otherObj) {
        otherObj.doIt(); // Yikes requires OtherObj declaration!
    }
};

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

person maccullt    schedule 13.09.2008
comment
На практике я не верю, что ваша первая пуля верна. Компилятор может игнорировать встроенный, но он имеет возможность только в том случае, если определение функции отображается на сайте вызова и это более или менее ограничивает вас функциями, локальными для текущего TU. - person Richard Corden; 17.09.2008

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

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

  • В VC ++ используйте ключевое слово __forceinline
  • В GCC используйте __attribute __ ((always_inline))

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

person Niall    schedule 14.09.2008
comment
Вы, вероятно, имеете в виду определение функций в файлах заголовков, а не объявление. - person Richard Corden; 17.09.2008
comment
Определение должно быть доступно, чтобы компилятор мог решить, будет ли он встроить функцию или нет. Intra-TU требует, чтобы определения были видны в файле заголовка, а для этого требуется ключевое слово inline. Таким образом, это возвращает нас к тому, что разработчик должен позвонить. - person Richard Corden; 17.09.2008
comment
На самом деле, большинство современных компиляторов полностью игнорируют этот намек. Можете ли вы подтвердить это источниками? В документации clang указано, что это рассматривается как подсказка, но, конечно, не игнорируется полностью. В документации MSVC указано нечто подобное. Ваше утверждение не подходит для двух самых популярных современных компиляторов. - person rdb; 20.09.2016
comment
@rdb: Сама подсказка, вероятно, бессмысленна, но ключевое слово inline также обещает, что все остальные вызывающие функцию также смогут увидеть определение. Отсутствие необходимости также выдавать отдельное определение функции может говорить в пользу встраивания. - person Peter Cordes; 29.09.2018

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

person Bronek    schedule 15.09.2008
comment
Да, это проблема, которую часто упускают из виду при экспорте встроенных функций из общей библиотеки! - person BeeOnRope; 26.09.2011
comment
Это также может быть преимуществом, потому что вашим пользователям не нужно ссылаться на файл .lib. Делает возможными однофайловые библиотеки, содержащие только заголовок, которые легко распространять и использовать. Это не хуже, чем библиотеки шаблонов, которые встречаются довольно часто. - person zett42; 29.09.2018

Я сомневаюсь. Даже компилятор автоматически встраивает некоторые функции для оптимизации.

person Vaibhav    schedule 13.09.2008
comment
Я бы расширил то, что сказал @Vaibhav, и объявил, что inline - это регистр 2000-х годов. Помните ключевое слово register? Кто-нибудь использовал его с 1980-х годов? - person Paul Tomblin; 14.09.2008
comment
@PaulTomblin: Стандарт C предусматривает, что несколько единиц перевода могут объявлять inline функции с одним и тем же именем, и рекомендует объединить их в общее определение; в большинстве случаев код, использующий inline, можно заставить работать с компилятором, который не распознает его через #define inline static, но использование inline на компиляторе, который понимает, что это, как правило, даст лучшие результаты, чем static. - person supercat; 27.07.2015

Я не знаю, связан ли мой ответ с вопросом, но:

Будьте очень осторожны со встроенными виртуальными методами! Некоторые компиляторы с ошибками (например, предыдущие версии Visual C ++) будут генерировать встроенный код для виртуальных методов, где стандартное поведение заключалось в том, чтобы ничего не делать, а идти вниз по дереву наследования и вызывать соответствующий метод.

person Stacker    schedule 16.09.2008

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

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

с компилятором VC ++ вы можете отменить это решение, используя __forceinline

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

person Fire Lancer    schedule 13.09.2008

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

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

(Упрощенно) Пример:

Представьте себе простую программу, которая просто вызывает функцию «X» 5 раз.

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

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

person Nick    schedule 14.09.2008
comment
Можете ли вы уточнить или предоставить пример (возможно, шаблон использования), показывающий, как большая встроенная функция приводит к большему количеству промахов в кеше? Предположим, у нас есть только одна большая встроенная функция, которая часто используется, разве это не уменьшит количество промахов кеша за счет увеличения локальности ссылки? - person nawK; 28.02.2020
comment
@nawK: Добавил пример. Когда функция достаточно велика, ваша локализация ссылки (для инструкций) может уменьшиться при встраивании, поскольку каждый встроенный экземпляр является отдельной копией. - person Nick; 20.04.2020

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

person Mladen Janković    schedule 13.09.2008

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

  • создавать нестабильность

    • Changing the source of an inline function causes all the users of the header to recompile
    • #includes просачивается в клиента. Это может быть очень неприятно, если вы переработаете встроенную функцию и удалите больше не используемый заголовок, на который полагался какой-то клиент.
  • размер исполняемого файла

    • Every time an inline is inlined instead of a call instruction the compiler has to generate the whole code of the inline. This is OK if the code of the function is short (one or two lines), not so good if the function is long
    • Некоторые функции могут производить намного больше кода, чем кажется на первый взгляд. В данном случае речь идет о «тривиальном» деструкторе класса, который имеет множество переменных-членов, не относящихся к поду (или две или 3 переменных-члена с довольно беспорядочными деструкторами). Вызов должен быть сгенерирован для каждого деструктора.
  • время исполнения

    • this is very dependent on your CPU cache and shared libraries, but locality of reference is important. If the code you might be inlining happens to be held in cpu cache in one place, a number of clients can find the code an not suffer from a cache miss and the subsequent memory fetch (and worse, should it happen, a disk fetch). Sadly this is one of those cases where you really need to do performance analysis.

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

person Tom Tanner    schedule 25.07.2016

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

  2. есть несколько других ситуаций, когда встроенный может не работать

    1. does not work in case of recursive function.
    2. Он также может не работать со статической переменной.
    3. он также не работает в случае использования цикла, переключателя и т. д. или мы можем сказать, что с несколькими операторами.
    4. И функция main не может работать как встроенная функция.
person Ankit-Bhalwal    schedule 24.07.2016