Встраивание функций - каковы примеры, когда это снижает производительность? - PullRequest
8 голосов
/ 27 апреля 2011

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

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

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

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

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

Ответы [ 7 ]

6 голосов
/ 27 апреля 2011

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

На некоторых платформах могут быть оптимизированы инструкции перехода для «ближнего кода». Этот тип прыжка использует смещение со знаком от текущей позиции. Подписанные смещения могут быть ограничены, например, 127 байтами. Прыжок в длину потребует большей инструкции, потому что более длинный прыжок должен включать абсолютный адрес. Более длинные инструкции занимают больше времени для выполнения.

Длинные встроенные функции могут увеличивать длину исполняемого файла, так что ОС должна перетаскивать новую «страницу» в память, называемую перестановкой страниц. Перестановка страниц замедляет скорость выполнения приложения.

Это «возможные» причины, по которым встроенный код может снизить производительность. Настоящая истина получается путем профилирования.

4 голосов
/ 27 апреля 2011

У меня был случай в нашем проекте на C (gcc). Мой коллега злоупотреблял строками в своей библиотеке, заставляя -fno-inline сократить время процессора на 10% (на SUN V890 с процессорами Ultrasparc IV +).

3 голосов
/ 28 апреля 2011

Что еще не упомянуто, так это то, что встраивание больших функций в другие большие функции может привести к чрезмерному распределению регистров, что ухудшит не только качество скомпилированного кода, но и добавит больше накладных расходов, чем было устранено встроенным (и даже макс. Винтом)В эвристиках глобальной и локальной оптимизации iirc msdn предупреждает об этом в __forceinline).Другие «конструкции», такие как встроенные не голые ассемблеры, вставленные в inlines, могут создавать ненужные стековые фреймы, или inline с особыми требованиями выравнивания, или даже те, которые просто выдвигают выделение стека в диапазон, в который компилятор добавляется при выделении стека проверки* под MSVC).

2 голосов
/ 27 апреля 2011

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

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

2 голосов
/ 27 апреля 2011

[В отношении встроенных функций]

Функция помещается в код, а не вызывается, аналогично использованию макросов (концептуально)

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

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

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

[Цитата зацепила SO-пользователя 'Fire Lancer', так что поверьте ему]

2 голосов
/ 27 апреля 2011

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

В целом, встраивание повышает производительность, устраняя вызов и возврат.

1 голос
/ 27 апреля 2011

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

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

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

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

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

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

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

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

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

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

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

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

...