Введение
Оптимизация, заменяющая вызов функции исходным кодом этой функции.
В вычислительной технике, подстановка функции (inline expansion), или инлайнинг, — это ручная или компиляторная оптимизация, которая заменяет место вызова функции телом вызываемой функции. Подстановка функции аналогична расширению макросов, но происходит во время компиляции, без изменения исходного кода (текста), в то время как расширение макросов происходит до компиляции и приводит к другому тексту, который затем обрабатывается компилятором. Инлайнинг — важная оптимизация, но оказывает сложное влияние на производительность. Как правило, умеренная подстановка функций улучшает скорость с незначительными затратами памяти, но чрезмерная подстановка функций снижает скорость, поскольку встроенный код занимает слишком много места в кэше инструкций и требует значительного объема памяти. Обзор немногочисленной академической литературы по инлайн-подстановке 1980-х и 1990-х годов представлен в Peyton Jones & Marlow 1999.
Обзор
Инлайн-развертывание аналогично макро-развертыванию, поскольку компилятор помещает новую копию функции в каждое место ее вызова. Инлайн-функции выполняются немного быстрее, чем обычные функции, поскольку устраняются накладные расходы на вызов функции, однако это приводит к увеличению потребления памяти. Если функция развернута инлайном 10 раз, в код будет вставлено 10 копий этой функции. Следовательно, инлайнинг наиболее эффективен для небольших функций, которые вызываются часто. В C++ члены-функции класса, если они определены внутри определения класса, разворачиваются инлайном по умолчанию (нет необходимости использовать ключевое слово `inline`); в противном случае ключевое слово необходимо. Компилятор может игнорировать попытку программиста развернуть функцию инлайном, особенно если она слишком велика. Инлайн-развертывание используется для устранения временных накладных расходов (избыточного времени) при вызове функции. Обычно оно применяется к функциям, которые выполняются часто. Оно также обеспечивает экономию памяти для очень маленьких функций и является базовым преобразованием для других оптимизаций. Без инлайн-функций компилятор решает, какие функции разворачивать. Программист имеет мало или совсем не имеет контроля над тем, какие функции развернуты, а какие нет. Предоставление такого контроля программисту позволяет использовать специфические для приложения знания при выборе функций для инлайнинга. Обычно, когда функция вызывается, управление передается ее определению посредством инструкции перехода или вызова. При инлайнинге управление переходит непосредственно к коду функции без использования инструкции перехода или вызова. Компиляторы обычно реализуют операторы с использованием инлайнинга. Условия и тела циклов требуют ленивых вычислений. Это свойство выполняется, когда код для вычисления условий и тел циклов развернут инлайном. Соображения производительности – еще одна причина для инлайнинга операторов. В контексте функциональных языков программирования инлайн-развертывание обычно сопровождается преобразованием бета-редукции. Программист может вручную развернуть функцию инлайном с помощью копирования и вставки кода, как однократную операцию с исходным кодом. Однако другие методы управления инлайнингом (см. ниже) предпочтительнее, поскольку они не приводят к ошибкам, возникающим, когда программист упускает из виду (возможно, измененную) дублированную версию исходного тела функции при исправлении ошибки в инлайнированной функции.
Поддержка компилятора
Компиляторы используют различные механизмы для определения, какие вызовы функций следует подставлять (inlining); они могут включать в себя указания от программистов для конкретных функций, а также общий контроль через параметры командной строки. Подстановка функций выполняется автоматически многими компиляторами во многих языках, основываясь на оценке целесообразности подстановки, в то время как в других случаях её можно указать вручную с помощью директив компилятора, обычно с использованием ключевого слова или директивы компилятора "inline". Как правило, это лишь рекомендация подставить функцию, а не требование, причём сила этой рекомендации варьируется в зависимости от языка и компилятора. Обычно разработчики компиляторов учитывают вышеупомянутые вопросы производительности и включают в свои компиляторы эвристические алгоритмы, которые выбирают, какие функции подставлять, чтобы повысить производительность, а не снизить её, в большинстве случаев.
Ограничения
Полное встроенное развертывание не всегда возможно из-за рекурсии: рекурсивное встроенное развертывание вызовов не завершится. Существуют различные решения, такие как развертывание на ограниченную глубину или анализ графа вызовов и разрыв циклов в определенных узлах (то есть, исключение развертывания некоторых ребер в рекурсивном цикле). Аналогичная проблема возникает при развертывании макросов, поскольку рекурсивное развертывание не заканчивается и обычно решается запретом рекурсивных макросов (как в C и C++).
Сравнение с макросами
Традиционно, в таких языках, как C, встроенное расширение осуществлялось на уровне исходного кода с помощью параметризованных макросов. Использование настоящих встроенных функций, доступных в C99, предоставляет ряд преимуществ по сравнению с этим подходом: в C вызовы макросов не выполняют проверку типов и даже не проверяют корректность аргументов, в то время как вызовы функций обычно выполняют. В C макрос не может использовать ключевое слово `return` с тем же смыслом, что и функция (это привело бы к завершению функции, запросившей расширение, а не самого макроса). Иными словами, макрос не может вернуть значение, отличное от результата последнего выражения, вычисленного внутри него. Поскольку макросы C используют просто текстовую подстановку, это может приводить к непредвиденным побочным эффектам и неэффективности из-за повторного вычисления аргументов и порядка операций. Ошибки компиляции в макросах часто сложно понять, так как они относятся к расширенному коду, а не к коду, который ввел программист. Таким образом, отладочная информация для встроенного кода обычно более полезна, чем для кода, расширенного макросами. Многие конструкции неудобно или невозможно выразить с помощью макросов, либо для этого требуется существенно отличающийся синтаксис. Встроенные функции используют тот же синтаксис, что и обычные функции, и могут быть легко встроены и исключены из встраивания по желанию. Многие компиляторы также могут встраивать некоторые рекурсивные функции; рекурсивные макросы, как правило, запрещены. Бьярне Страуструп, создатель C++, подчеркивает, что макросов следует избегать, где это возможно, и выступает за широкое использование встроенных функций.
In C, macro invocations do not perform type checking, or even check that arguments are well formed, whereas function calls usually do. In C, a macro cannot use the return keyword with the same meaning as a function would do (it would make the function that asked the expansion terminate, rather than the macro). In other words, a macro cannot return anything which is not the result of the last expression invoked inside it. Since C macros use mere textual substitution, this may result in unintended side effects and inefficiency due to re evaluation of arguments and order of operations. Compiler errors within macros are often difficult to understand, because they refer to the expanded code, rather than the code the programmer typed. Thus, debugging information for inlined code is usually more helpful than that of macro expanded code. Many constructs are awkward or impossible to express using macros, or use a significantly different syntax. Inline functions use the same syntax as ordinary functions, and can be inlined and un inlined at will with ease. Many compilers can also inline expand some recursive functions; recursive macros are typically illegal. Bjarne Stroustrup, the designer of C++, likes to emphasize that macros should be avoided wherever possible, and advocates extensive use of inline functions.
Методы отбора
Многие компиляторы активно подставляют функции непосредственно в код, где это целесообразно. Хотя это может приводить к увеличению размера исполняемых файлов, агрессивное подстраивание функций становится все более востребованным, поскольку объём памяти растёт быстрее, чем скорость процессора. Подстраивание функций — важнейшая оптимизация в функциональных и объектно-ориентированных языках программирования, которые используют её для обеспечения достаточного контекста для своих обычно небольших функций, чтобы классические оптимизации могли быть эффективными.
C и C++
В C и C++ есть ключевое слово `inline`, которое выполняет роль как директивы компилятора, указывающей на желательность встраивания, но не гарантирующей его, так и влияет на видимость и связывание. Изменение видимости необходимо для того, чтобы функция могла быть встроена стандартным инструментарием C, где компиляция отдельных файлов (точнее, единиц трансляции) происходит с последующей связью: для того чтобы компоновщик мог встраивать функции, они должны быть объявлены в заголовочном файле (чтобы быть видимыми) и помечены как `inline` (чтобы избежать неоднозначности из-за множественных определений).