Межпроцедурная оптимизация (), или полнопрограммная оптимизация программ () — оптимизация компилятора, которая использует глобальный анализ потока управления и затрагивает множество процедур, даже находящихся в разных модулях, за счёт чего может достигаться существенный прирост быстродействия.
С ростом объёма программ разработчики стали делать свой код всё более удобочитаемым и повторно используемым. Зачастую это приводит к тому, что процедуры становятся предельно общими, в то время как в конкретной программе можно обойтись и частным случаем. Задача межпроцедурной оптимизации — именно генерация таких частных случаев.
Межпроцедурная оптимизация выполняется компилятором автоматически (иногда с указанием специальных директив). Её активация может приводить к существенному увеличению времени компиляции. К компиляторам, умеющим выполнять указанную оптимизацию, относятся MLton и MLKit для Standard ML, для Scheme, JHC для Haskell, Intel C++ Compiler.
Примеры
Замена параметра функции константой Пройдя по коду, компилятор убеждается, что один из параметров всегда константа, и уничтожает его.
Было void DoSomething(Object* aObj, int aParam) { if (aObj==NULL) throw logic_error("aObj==NULL"); cout << "DoSomething(" << aObj->name() << "," << aParam << ")" << endl; }
int main() { Object obj1, obj2;
DoSomething(&obj1, 1); DoSomething(&obj2, 1);
return 0; }
Стало void DoSomething(Object* aObj) { if (aObj==NULL) throw logic_error("aObj==NULL"); cout << "DoSomething(" << aObj->name() << "," << 1 << ")" << endl; }
int main() { Object obj1, obj2;
DoSomething(&obj1); DoSomething(&obj2);
return 0; }
Замена виртуального вызова статическим Здесь компилятор убеждается, что все реально выполняющиеся виртуальные вызовы ведут к вызову одной и той же функции. Вместо того, чтобы обращаться к таблице виртуальных методов, компилятор делает прямой вызов функции.
В том же примере, если Object::name() — виртуальный метод, оптимизированная функция будет выглядеть так.
void DoSomething(Object* aObj) { if (aObj==NULL) throw logic_error("aObj==NULL"); cout << "DoSomething(" << Object::name(aObj) << "," << 1 << ")" << endl; }
Удаление незадействованного кода После удаления получится:
void DoSomething(Object* aObj) { cout << "DoSomething(" << Object::name(aObj) << "," << 1 << ")" << endl; }
Заодно может происходить чистка таблиц виртуальных методов.
Инлайнинг Если функция используется однократно, она напрямую включается в то место, из которого она вызывается.
Небольшие функции также можно напрямую включать в вызывающий код.
Многие языки программирования (Паскаль, Java, D) не имеют ключевого слова inline, и решение инлайнировать функцию принимается оптимизатором (в случае Java — обфускатором).
Было inline int DoSomething(int aParam) { return aParam * aParam; }
int main() { int x = 2; int y = 3;
cout << x << "^2=" << DoSomething(x) << ", " << y << "^2=" << DoSomething(y) << endl;
return 0; }
Стало int main() { int x = 2; int y = 3;
cout << x << "^2=" << x*x << ", " << y << "^2=" << y*y << endl;
return 0; }
Ссылки Thomas C. Spillman, «Exposing side effects in a PL/I optimizing compiler», in Proceedings of IFIPS 1971, North-Holland Publishing Company, pages 376—381. Frances E. Allen, «Interprocedural Data Flow Analysis», IFIPS Proceedings, 1974. Frances E. Allen, and Jack Schwartz, «Determining the Data Flow Relationships in a Collection of Procedures», IBM Research Report RC 4989, Aug. 1974. Philip Abrams, «An APL Machine», Stanford University Computer Science Department, Report STAN-CS-70-158, February, 1970. Terrence C. Miller, «Tentative Compilation: A Design for an APL Compiler», Ph.D. Thesis, Yale University, 1978.
Категория:Оптимизации компилятора