Введение
Метод оптимизации компьютерной программы Межпроцедурная оптимизация (IPO) - это набор компиляторных методов, используемых в компьютерном программировании для улучшения производительности в программах, содержащих много часто используемых функций небольшой или средней длины. IPO отличается от других оптимизаций компилятора анализом всей программы, а не одной функции или блока кода. IPO стремится уменьшить или устранить дублирование вычислений и неэффективное использование памяти и упростить итеративные последовательности, такие как петли. Если в рамках цикла происходит вызов к другой процедуре, анализ IPO может определить, что лучше всего встроить эту процедуру. Кроме того, IPO может перезаказать процедуры для лучшего расположения памяти и локализации. IPO может также включать в себя типичные оптимизации компилятора, применяемые на уровне всей программы, например, устранение мертвого кода (DCE), которое удаляет код, который никогда не выполняется. IPO также пытается обеспечить лучшее использование констант. Современные компиляторы предлагают IPO в качестве опции во время компиляции. Фактический процесс IPO может происходить на любой стадии между человеком читаемый исходный код и производство готовой исполняемой двоичной программы. Для языков, которые компилируют файл за файлом, эффективное IPO через единицы перевода (файлы модулей) требует знания "точек входа" программы, чтобы можно было запустить целую оптимизацию программы (WPO). Во многих случаях это реализуется как пропуск оптимизации времени ссылки (LTO), потому что вся программа видима для ссылку.
Interprocedural optimization (IPO) is a collection of compiler techniques used in computer programming to improve performance in programs containing many frequently used functions of small or medium length. IPO differs from other compiler optimizations by analyzing the entire program as opposed to a single function or block of code. IPO seeks to reduce or eliminate duplicate calculations and inefficient use of memory and to simplify iterative sequences such as loops. If a call to another routine occurs within a loop, IPO analysis may determine that it is best to inline that routine. Additionally, IPO may re order the routines for better memory layout and locality. IPO may also include typical compiler optimizations applied on a whole program level, for example dead code elimination (DCE), which removes code that is never executed. IPO also tries to ensure better use of constants. Modern compilers offer IPO as an option at compile time. The actual IPO process may occur at any step between the human readable source code and producing a finished executable binary program. For languages that compile on a file by file basis, effective IPO across translation units (module files) requires knowledge of the "entry points" of the program so that a whole program optimization (WPO) can be run. In many cases, this is implemented as a link time optimization (LTO) pass, because the whole program is visible to the linker.
Анализ
Цель любой оптимизации скорости - запустить программу как можно быстрее; проблема в том, что компилятор не может правильно проанализировать программу и определить, что она будет делать, а тем более, что программист намеревался сделать. В отличие от этого, программисты-люди начинают с другого конца с целью и пытаются создать программу, которая достигнет ее, желательно, не затрачивая много мысли в процессе. По разным причинам, включая читабельность, программы часто разбиваются на ряд процедур, которые обрабатывают несколько общих случаев. Однако общность каждой процедуры может привести к трате усилий в конкретных случаях. Межпроцедурная оптимизация представляет собой попытку уменьшить эту потерю. Предположим, что существует процедура, которая оценивает F ((x), и что F является чистой функцией, и код запрашивает результат F ((6) и затем позже, F ((6) снова. Эта вторая оценка почти наверняка ненужна: результат можно было сохранить и использовать позже. Эта простая оптимизация препятствует реализации F ((x) становится нечистой; то есть, его выполнение включает ссылки на параметры, кроме явного аргумента 6, который был изменен между вызовами, или побочные эффекты, такие как печать какого-то сообщения в журнале, подсчет количества оценок, накопление потребляемого времени процессора, подготовка внутренних таблиц, чтобы последующие вызовы для связанных параметров были облегчены и так далее. Устранение этих побочных эффектов путем повторного невыявления может быть приемлемым, а может и нет. Более обще, помимо оптимизации, вторая причина использования процедур - избежать дублирования кода, который приводит к одинаковым результатам или почти одинаковым результатам каждый раз, когда выполняется процедура. Поэтому общий подход к оптимизации заключается в том, чтобы изменить это: некоторые или все вызовы определенной процедуры заменяются соответствующим кодом, при этом параметры подменяются соответствующим образом. Затем компилятор попытается оптимизировать результат.
WPO и LTO
Целая оптимизация программы (WPO) - это оптимизация компилятора программы с использованием информации обо всех модулях в программе. Обычно оптимизации выполняются на основе модуля, "компиляции"; но этот подход, хотя и легче писать и тестировать и менее требовательный к ресурсам во время самой компиляции, не позволяет быть уверенным в безопасности ряда оптимизаций, таких как агрессивная инлайнинга, и, таким образом, не может выполнять их, даже если они фактически окажутся повышением эффективности, которое не изменяет семантику испускаемого объектного кода. Оптимизация времени ссылки (LTO) - это тип оптимизации программы, выполняемый компилятором для программы во время ссылки. Оптимизация времени ссылки актуальна в языках программирования, которые компилируют программы на основе файлов по файлам, а затем связывают эти файлы вместе (например, C и Fortran), а не все сразу (например, Java просто в компиляции времени (JIT)). После того, как все файлы были компилированы отдельно в объектные файлы, традиционно компилятор связывает (сливает) объектные файлы в один файл, исполняемый файл. Однако в LTO, реализованном GNU Compiler Collection (GCC) и LLVM, компилятор способен сбрасывать свое промежуточное представление (IR), то есть GIMPLE байт-код или LLVM-бит-код, соответственно, так что все различные компиляционные единицы, которые будут составлять один исполняемый файл, могут быть оптимизированы как один модуль, когда соединение наконец произойдет. Это расширяет сферу межпроцедурных оптимизаций, чтобы охватить всю программу (или, скорее, все, что видно во время ссылки). С оптимизацией времени ссылки компилятор может применять различные формы межпроцедурной оптимизации ко всей программе, позволяя более глубокий анализ, большую оптимизацию и, в конечном счете, лучшую производительность программы. На практике LTO не всегда оптимизирует всю программу. Функции библиотеки, особенно динамически связанные общие объекты, намеренно не используются, чтобы избежать чрезмерного дублирования и позволить обновление. Статическая ссылка, естественно, подходит для концепции LTO, но она работает только с библиотечными архивами, которые содержат объекты IR, а не только файлы объектов машинного кода. И конечно, когда программа, которая создается, сама является библиотекой, оптимизация сохранит каждый внешне доступный (экспортированный) символ, не пытаясь слишком сильно удалить их как часть DCE.
В общем
Этот пример чрезвычайно прост, хотя осложнения уже очевидны. Скорее всего, это будет случай многих процедур, имеющих различные вычитаемые или объявленные программистом свойства, которые могут позволить оптимизации компилятора найти некоторое преимущество. Любой параметр процедуры может быть только считанным, записанным, как считанным, так и записанным, или полностью игнорироваться, что дает возможность, например, постоянным, не нуждающимся в защите через временные переменные, но то, что происходит в любом заданном вызове, может вполне зависеть от сложной сети соображений. Другие процедуры, особенно функции, подобные процедурам, будут иметь определенное поведение, которое при конкретных вызовах может позволить избежать некоторой работы: например, гамма-функция, если она вызвана с целым параметром, может быть преобразована в расчет, включающий целые факториалы. Некоторые компьютерные языки позволяют (или даже требуют) утверждения относительно использования параметров и могут дополнительно предоставить возможность заявить, что переменные имеют свои значения, ограниченные некоторыми наборами (например, 6 < x ≤ 28), тем самым обеспечивая дополнительную корму для процесса оптимизации, а также обеспечивая полезную проверку согласованности исходного кода для обнаружения ошибок. Но этого никогда не достаточно. Некоторым переменным могут быть даны простые ограничения, в то время как другим потребуются сложные спецификации: как можно указать, что переменная P должна быть простым числом, и если да, то включено ли значение 1 или нет? Осложнения возникают сразу: каковы действительные диапазоны для дня месяца D, если M - число месяца? И все ли нарушения заслуживают немедленного прекращения? Даже если бы все это удалось решить, какая польза могла бы быть? И какой ценой? Полные спецификации будут представлять собой пересказ функции программы в другой форме, и, помимо времени, которое компилятор будет тратить на их обработку, они будут подвержены ошибкам. Вместо этого допускаются только простые спецификации с проверкой диапазона времени выполнения. В случаях, когда программа не читает входные данные (как в примере), можно представить, что анализ компилятора будет перенесен вперед, так что результатом будет не более чем серия выписываемых инструкций, или, возможно, некоторые петли, которые целесообразно генерируют такие значения. Может ли он тогда распознать программу для генерации простых чисел и перейти к наиболее известному методу для этого или вместо этого представить ссылку на библиотеку? Вряд ли! В общем, возникают произвольно сложные соображения (проблема Entscheidungs), чтобы исключить это, и нет другого выбора, кроме как запустить код только с ограниченными улучшениями.
История
Для процедурных языков, таких как ALGOL, межпроцессуальный анализ и оптимизация, по-видимому, вошли в коммерческую практику в начале 1970-х годов. Компилятор оптимизации PL/I IBM выполнял межпроцедурный анализ для понимания побочных эффектов как вызовов процедур, так и исключений (в терминах PL/I как "на условиях") и в статьях Фран Аллена. Работа по компиляции языка программирования APL была обязательно межпроцедурной. Методы межпроцедурного анализа и оптимизации были предметом академических исследований в 1980-х и 1990-х годах. Они вновь появились в коммерческом мире компиляторов в начале 1990-х годов с компиляторами как от Convex Computer Corporation ("Компилятор приложений" для Convex C4), так и от Ardent (компилятор для Ardent Titan). Эти компиляторы показали, что технологии могут быть достаточно быстрыми, чтобы быть приемлемыми в коммерческом компиляторе; впоследствии межпроцедурные методы появились в ряде коммерческих и некоммерческих систем.
Unix-подобный
GNU Compiler Collection имеет функцию inlining на всех уровнях оптимизации. При этом применяется только к тем, кто только один раз вызван, при этом ограничение ослаблено По умолчанию это поведение только одного файла, но с оптимизацией времени ссылки это становится целой программой. Файлы объектов, созданные LTO, содержат промежуточное представление (IR), специфичное для компилятора, которое интерпретируется во время связи. Чтобы убедиться, что это хорошо работает со статическими библиотеками, новые линкеры GNU имеют интерфейс "связующего плагина", который позволяет компилятору преобразовывать объектные файлы в форму машинного кода при необходимости. Этот плагин также помогает управлять процессом LTO в целом. Альтернативно, можно создать "жирный LTO" объект, содержащий как машинный код, так и ИР, но это занимает больше места. но LLVM делает это возможным для Rust и всех других компиляторов на основе LLVM.
Опции, не относящиеся к LTO
GCC и Clang по умолчанию выполняют IPO на уровне оптимизации 2. Однако степень оптимизации ограничена, когда LTO отключен, поскольку IPO может происходить только в пределах файла объекта, а нестатические функции никогда не могут быть устранены. Последняя проблема имеет решение, не связанное с LTO: переключатель может быть использован, чтобы предположить, что он не статичен, то есть видим снаружи. Другой метод, не относящийся к LTO, - это "функциональные секции" (в GCC и Clang). Помещая каждую функцию в свой раздел в объектном файле, ссылочный модуль может выполнять удаление мертвого кода без IR, удаляя не ссылающиеся разделы (с помощью опции ссылочного модуля). Аналогичный вариант доступен для переменных, но он приводит к созданию гораздо худшего кода.
Другое
Компиляторы Intel C/C++ позволяют выводить всю программу на IPO. Флаг для обеспечения межпроцедурной оптимизации для одного файла - , флаг для обеспечения межпроцедурной оптимизации для всех файлов в программе - компилятор MSVC, интегрированный в Visual Studio, также поддерживает межпроцедурную оптимизацию для всей программы. Независимый от компилятора интерфейс для обеспечения межпроцедурной оптимизации всей программы используется через свойство в CMake.
The MSVC compiler, integrated into Visual Studio, also supports interprocedural optimization on the whole program. A compiler independent interface for enabling whole program interprocedural optimizations is via the property in CMake.