40. Использование оптимизирующего компилятора. Принципы и приемы оптимизации работа с оптимизирующими компиляторами
Оптимизация - как оптимизировать программы
Оптимизация, все всяких сомнений, является неотъемлемой частью разработки практически любой программы. Если ваша программа хорошо делает то, для чего она предназначена – это хорошо, но если она ещё делает это быстро, т.е. программа оптимизирована – то это вдвойне хорошая программа. Но, при этом, следует чётко понимать, когда необходимо заниматься оптимизацией, а когда оптимизация превращается в пустую трату времени. Я почти в каждом уроке пишу о том, что я не сторонник ранней оптимизации ПО, но всё равно читатели блога часто мне пишут о том, что им хотелось бы, что бы я рассматривал и вопросы ускорения работы программ, рассказал о тонкостях этого непростого дела и дал хотя бы общие рекомендации. В общем, я решил удовлетворить эти просьбы и немного рассказать о том, как я лично делаю оптимизацию, когда я её делаю и почему я это делаю. А так же о том, как не потерпеть крах и не потратить время впустую, напоровшись на подводные камни.
Оптимизировать надо то, что тормозит
С моей точки зрения это и есть главное правило оптимизации. Очень часто я встречаю подход, при котором оптимизируется абсолютно весь код, на это уходят часы, а иногда даже недели или месяцы работы, и при этом особого результата не заметно. Обычно это происходит от того, что начинающие программисты считают, что исходный код программы будет работать быстро, если его перелопатить сверху до низу и выжать из него каждую микросекунду. Я не могу сказать, что такой подход к разработке программ совсем уж не верный – в некоторых ситуациях так делать и нужно. Но эти ситуации встречаются редко, гораздо чаще встречаются ситуации, когда из многих тысяч строк программы достаточно переработать лишь десяток-два строк кода и получить огромный прирост производительности.
Профайлинг – ключ к успешной оптимизации
Что бы начинать оптимизацию программы, прежде всего необходимо понять, какие именно участки кода создают не нужные “тормоза” – как правило “тормозит” от 1% до 5% кода, весь остальной код просто не оказывает никакого влияния на производительность. Проще всего найти эти участки кода запустив профайлер. Проще всего использовать встроенный в Visual Studio, либо Intel Perfomance Analyzer. Как правило, достаточно 1 раз запустить вашу программу под профайлером и заставить её поработать от нескольких секунд до нескольких минут, а потом просто посмотреть отчёт, сформированный профайлером. Из этого отчёта Вам понадобится лишь несколько первых строк, в которых и находятся те самые участки программы (функции), которые занимают больше всего процессорного времени – именно их и надо оптимизировать в первую очередь. И лишь после того, как Вы это сделаете, надо судить о том, стоит ли продолжать процесс оптимизации или уже пора остановиться – незачем тратить часы и дни работы на то, что бы, например, поднять ФПС игры с 90 до 91 кадра в секунду.
Методы оптимизации
Существую несколько достаточно простых правил, которые позволяют ускорить работу вашей программы, т.е. прооптимизировать её. Как правило, это следующие методы:
- Локализация данных
- Векторизация
- Минимизация вызова функций
- Инлайн-функции
Оптимизация через локализацию данных
Как известно, CPU обычно имеет внутри себя от одного до нескольких уровней кешей – с ними процессор работает намного (в разы/десятки/сотни раз) быстрее, чем с обычной оперативной памятью. Как только процессор обращается к каким-то данным, которые находятся вне его кеша – он читает эти данные из памяти, располагает их в кеше и потом работает с ними. Работать напрямую с памятью он обычно не умеет. При этом читает он обычно не только те данные, которые ему нужны в данный момент, но и небольшой “запас”, предполагая при этом, что они тоже могут понадобиться. Процесс чтения данных из обычной памяти в кеш не слишком быстрый (если даже не сказать что он очень медленный), а процесс чтения этих данных потом из кеша в процессор – быстрый.
Соответственно, если расположить обрабатываемые данные в памяти таким образом, что бы они, при работе, обрабатывались последовательно, то работа с такими данными может оказаться в сотни раз быстрее, чем при работе с данными, разбросанными по всем концам оперативной памяти.
Соответственно, совет в данном случае очень простой – просто располагайте данные в памяти последовательно. Например, не создавайте сотни объектов через оператор new, а создавайте сразу массив с помощью оператора new[]. Если количество создаваемых объектов велико (сотни, тысячи), то вы можете получить существенный прирост производительности – иногда даже в сотни раз.
Оптимизация через векторизацию
Если ваши данные локализованы в одном участке памяти и вам необходимо обработать массив этих данных, то очень часто может помочь оптимизация через векторизацию. Обычно всё, что требуется с вашей стороны – это включить соответствующие опции компилятора, как правило это: разрешить использовать SSE/SSE2 и указать максимальный уп=ровень оптимизации. После этого, при обработке массивов данных (в цикле), компилятор сам постарается сделать специальный код, которые будет использовать SSE-инструкции для обработки массивов – при больших размерах массива, часто это может привести к ускорению работы программы в несколько раз.
Оптимизация с помощью минимизации вызова функций
Обычно вызов каждой функции требует от центрального процессора поместить данные, передаваемые функции (параметры функции) в “стек”, вызывать функцию и прочитать результат работы функции (возвращаемое значение) из стека. Фактически, вызов каждой, даже самой простой, функции превращается в кучу работы. Соответственно, если Вы, вместо того, что бы вызывать функцию, опишите операции, которые надо провести над данными непосредственно, без вызова функции – скорее всего вы получите прирост производительности. При этом, как правило, такую операцию имеет смысл делать лишь для достаточно небольших функций – размером не больше нескольких (до десяка-двух) строк кода, не более – для более больших функций разница в скорости будет уже практически незаметна. И наоборот – чем меньше функция, тем более заметен будет результат. При этом надо заметить, что часто компилятор (при включённых опциях оптимизации) сам проводит такую оптимизацию и получает хороший код без участия программиста. Но он не всегда может сам до этого догадаться, потому ему иногда надо помогать, просто прооптимизировав код вручную.
Оптимизация с помощью инлайн-функций
Инлайн-функции, это такие функции, которые компилятор пытается встроить непосредственно в код, вместо создания вызова функции. Потому это правило оптимизации является прямым следствием предыдущего. Обычно для того, что бы функция заинлайнилась надо указать при её декларации ключевое слово inline или __inline – и компилятор заинлайнит её, если посчитает это разумным.
Когда не надо оптимизировать программу?
Ответ на этот вопрос можно дать простой и короткий – программу не надо оптимизировать до того момента, пока работа над ней (её функционалом) полностью не законченна. Т.е. сначала необходимо реализовать полностью весь функционал программы (или её отдельного модуля), потом убедиться что эта программа (или модуль) реально требует оптимизации и лишь после этого приступать к делу.
Если начать делать оптимизацию до того, как вы завершили основную часть работы над функционалом – очень высока вероятность того, что Вам позже придётся вносить какие-то дополнительные изменения в уже прооптимизированный участок кода, а это приведёт одновременно к тому, что вы будете путаться (читать и понимать оптимизированный код обычно сложнее) и при этом Вам потом ещё придётся повторно проводить оптимизацию (т.к. Вы поменяете код и не факт, что после этого он будет работать так же быстро, как и прежде).
Иными словами, Вы будете делать двойную, тройную, и более, работу – старайтесь оптимизировать не только производительность, но и своё рабочее время – его заменить нечем. Вместо того, что бы “разгонять” совершенно не важный участок программы, возможно, следует заняться более важными вещами – реализацией новых функций, повышением удобства пользовательского интерфейса, поиском багов и т.д.
Именно по этой причине я сам всегда провожу работы по ускорению программы лишь в самую последнюю очередь, чего и Вам советую
Кстати, после оптимизации обязательно не забудьте ещё раз протестировать ваш исходный код на предмет отсутствия багов – очень часто даже опытные программисты, в погоне за производительностью, допускают много мелких и сложнонаходимых ошибок – будьте внимательны!
P/S Как-нибудь я постараюсь найти время и рассказать и показать на примерах различные виды оптимизаций, в том числе не только самого кода, но и графики, чтения с дисков и прочего.
Ещё по этой теме:
dev.mindillusion.ru
Тема 2.8. Оптимизация программ. Оптимизирующие компиляторы.
Читайте также:
|
Понятие оптимизации программ
Оптимизация программы — это улучшение какой-либо характеристики программы, называемой критерием оптимизации. Оптимизация … программ в основном выполняется по двум основным критериям: быстродействие и объему используемых данных.
Производительность приложения определяется самым узким его участком, поэтому в первую очередь нужно определить части программы, на которых будет выполняться оптимизация. Процесс оптимизации следует начать с профилировки программы. Профилировкой называют измерение производительности как всей программы, так и отдельных ее фрагментов, с целью нахождения «горячих точек» — тех участков программы, на выполнение которых расходуется наибольшее количество времени. При этом важно отметить, что ликвидация не самых «горячих» точек программы, практически не увеличивает ее быстродействия.
Основная цель профилировки – это исследование характера поведения приложения во всех его точках. В зависимости от степени детализации в качестве «точки» рассматривается как отдельная машинная команда, так и целая конструкция высокого языка — функция, цикл, процедура. Сложная программа состоит из большого числа функций. Нет смысла оптимизировать их все – трудоемкость такого подхода будет выше выгод, полученных от оптимизации программы целиком. Для начала необходимо локализовать участки кода с максимальной вычислительной трудоемкостью. Участки программы, которые в наибольшей степени влияют на ее производительность, в силу наиболее частого выполнения или своей ресурсоемкости называются критическим кодом. В поиске критического кода программы используют профайлеры (профилировщики) – специальные программы, которые измеряют временные затраты на выполнение участков кода программы. Профилировщики представляют возможности для оптимизации программ. К таким программам относятся Intel VTune, AMD Code Analyst, profile.exe и множество других. Наиболее мощным из них на сегодняшний день является пакет от Intel. Эта программа позволяет измерить время обработки каждой команды и вывести полную статистику о состоянии процессора при выполнении каждой команды.
Большинство современных профилировщиков поддерживают следующий набор базовых операций:
• определение общего времени исполнения каждой точки программы;
• определение удельного времени исполнения каждой точки программы;
• определение причины и/или источника конфликтов;
• определение количества вызовов той или иной точки программы;
• определение степени покрытия программы.
Основные правила оптимизации:
1. Прежде чем приступать к оптимизации, необходимо иметь надежно работающий неоптимизированный вариант.
3. Обнаружив профилировщиком узкие места необходимо произвести оптимизацию в рамках языка высокого уровня.
Возможны ситуации, где в неудовлетворительной производительности кода виноваты процессор или подсистема памяти, а не компилятор. Лишь после анализа листинга следует приступать к ассемблерной оптимизации.
Оптимизация начинается с выделения профилировщиком критического кода и анализа его неоптимальности. Причем каждое внесенное изменение необходимо проверять профилировщиком. После завершения оптимизации локального фрагмента программы, необходимо выполнить контрольную профилировку всей программы целиком на предмет обнаружения новых появившихся «горячих точек».
Проводя оптимизацию, не следует забывать о ее цели. Фактически идеал недостижим, поэтому оптимизацию следует завершать когда:
1. Производительность программы признана удовлетворяющей;
2. В программе отсутствуют «горячие точки», то есть количество инструкций равномерно распределено по все программе, и дальнейшая оптимизация потребует переписывания большого количества кода;
3. Сложность алгоритма настолько высока, что не представляется возможным дальнейшая оптимизация без значительных временных затрат;
4. Критическая зависимость от платформы, когда дальнейшая машинно-зависимая оптимизация приведет к потере совместимости с одной из целевых платформ.
Ко всем методам оптимизации алгоритма предъявляются следующие требования:
1. оптимизация должна быть по возможности максимально машинно- независимой и переносимой на другие платформы (операционные системы) без существенных потерь эффективности.
2. оптимизация не должна увеличивать трудоемкость разработки (в том числе тестирования) приложения более чем на 10-15%.
3. оптимизирующий алгоритм должен давать выигрыш не менее чем на 20-25% в скорости выполнения.
4. оптимизация не должна допускать безболезненное внесение изменений.
Алгоритмические приемы оптимизации
Приемы оптимизации программы можно разделить на алгоритмические и машинно-зависимые способы. В случае использования алгоритмических приемов оптимизации используются различные математические и логические методы для улучшения параметров алгоритма. Такой способ оптимизации невозможно автоматизировать, успешность его применения зависит от программиста. Способность программиста к алгоритмической оптимизации программы зависит от его понимания предметной области: владения им базовых концепций применяемых алгоритмов и особенностей предметной области программы.
В первую очередь это замена алгоритмов на более быстродействующие. Часто бывает, что более простой алгоритм показывает низкую производительность по сравнению с более сложными. Тогда, возможна замена эквивалентных алгоритмов, например, замена пузырьковой сортировки массива на быструю сортировку.
В некоторых случаях возможна оптимизация программы за счет снижение точности. В зависимости от особенностей предметной области возможно уменьшить разрядность представления чисел или перейти от выполнения операций с числами с плавающей запятой к целым числам или числам с фиксированной запятой.
На практике используется весьма широкий набор машинно-независимых оптимизирующих преобразований, что связано с большим разнообразием неоптимальностей. К ним относятся:
• разгрузка участков повторяемости — это такой способ оптимизации, который состоит в вынесении вычислений из многократно исполняемых участков программы на участки программы, редко исполняемые. К этому виду преобразования относятся различные чистки зон, тел циклов и тел рекурсивных процедур, когда инвариантные по результату выполнения выражения, исполняемые при каждом прохождении участка повторяемости, выносятся из него. Если размещение осуществляется перед входом в участок повторяемости, то эту ситуацию называют чисткой вверх, если же за выходом из участка повторяемости, то чисткой вниз
• упрощение действий — этот способ оптимизации ориентирован на улучшение программы за счет замены групп (как правило, удаленных друг от друга) вычислений на группу вычислений, дающий тот же результат с точки зрения всей программы, но имеющих меньшую сложность.
• чистка программы — данный способ повышает качество программы за счет удаления из нее ненужных объектов и конструкций. Набор преобразований этого типа включает в себя следующие оптимизации: удаление идентичных операторов, удаление из программы операторов, недостижимых по управлению от начального, удаление несущественных операторов, то есть операторов не влияющих на результат программы, удаление процедур, к которым нет обращений, удаление неиспользуемых переменных и другие.
• экономия памяти и оптимизация работы с памятью — улучшения быстродействия возможно за счет уменьшения объема памяти, отводимой под информационные объекты программы в каждом ее исполнении.
• реализация действий — это способ повышения быстродействия программы за счет выполнения определенных ее вычислений на этапе трансляции.
• сокращение программы и другие методы.
Машинно-зависимые приемы оптимизации
Машинно-зависимые используют особенности устройства и работы конкретной системы. Ярким примером машинно-зависимой оптимизации является векторизация операций, т.е. использование потоковых расширений процессора, таких как MMX (MultiMedia eXtensions), SSE (Streaming SIMD Extensions) и т.п. Машино-зависимую оптимизацию можно выполнять двумя различными способами. Первый способ основан на понимании работы кодогенератора компилятора, его алгоритма и рекомендуется для приложений, в которых компилятор выбирается в начале проекта и в дальнейшем не меняется. При использовании такого способа преобразуется исходный код программы, написанный на языке высокого уровня. Для тех проектов, в которых заранее не известен компилятор (OpenSource проекты, кроссплатформенные приложения) применятся другой способ, основанный на замещении ресурсоемких участков кода ассемблерными вставками. При такой оптимизации ухудшается переносимость кода на другие платформы. Машинно-зависимые способы оптимизации довольно хорошо автоматизируются и большую часть их выполняют оптимизирующие компиляторы. Однако всегда остаются моменты в программе, которые можно оптимизировать вручную.
refac.ru
40. Использование оптимизирующего компилятора.
Оптимизирующий компилятор - это тот, в котором используются различные методы получения более оптимального программного кода при сохранении его функциональных возможностей. Наиболее распространённые цели оптимизации - это сокращение времени выполнения программы, повышение производительности, экономия памяти, минимизация энергозатрат, уменьшение количества операций ввода-вывода и другие. Оптимизация может происходить неявно во время трансляции программы, но как правило считается отдельным этапом работы компилятора. Компоновщики так же могут выполнять часть оптимизирующей работы, таких как удаление неиспользуемых подпрограмм или их переупорядочивание. Различают низко- и высокоуровневую оптимизацию. Низкоуровневая оптимизация преобразовывает команду на уровне элементарных команд. Например, инструкций процессора конкретной архитектуры Высокоуровневая оптимизация осуществляется на уровне структурных элементов программы, таких как модули, функции, ветвление, циклы. Рассмотрим компилятор g++, который имеет несколько опций оптимизаций.
-O0
-O1
-O2
-O3
Каждая из этих опций включает флаги оптимизации и каждый из флагов представляет собой включение/выключение определённого вида оптимизаций. Каждая следующая опция будет компилировать дольше, но эффективнее. По умолчанию используется -O0. Кроме того, существует опция -Os, которая призвана уменьшить размер кода, и -Og, которая объединяет быструю компиляцию и удобную отладку.
41. Разработка универсального по. Основные приёмы разработки универсального по.
Под универсальностью понимают независимость программы от конкретного набора данных. Написать полностью универсальную программу невозможно. Условия универсальности:
1. Без необходимости не использовать константных значений.
Для того, чтобы уйти от констант, можно написать функцию инициализации, в которой всем переменным, которые требуют присвоения начальных значений, будут присвоены значения, считанные из файла инициализации.
2. Не использовать в программах стандартные имена устройств, имена объектов файловой системы.
3. Для разработки универсальных программ не следует использовать статическое выделение памяти.
4. Для повышения переносимости с одной аппаратной платформы на другую, не рекомендуется пользоваться числовыми значениями размеров типов или объектов
5. Максимально использовать безтиповые указатели с дальнейшим их преобразованием в нужный тип
6. Создание и реализация полиморфных функций методом перегрузки.
7. Универсализации способствует создание собственных библиотек.
8. Использование при разработке ПО таблиц, таких как FAT, NTFS, таблица векторов прерываний, приоритетов и т.д.
42. Понятие отладки, основные стратегии проведения отладки.
Отладка - это этап разработки ПО, на котором локализуют и исправляют ошибки. Отладка и тестирование тесно связаны и вместе с тем это разные этатапы разработки ПО. Типичный цикл, который многократно повторяется включает:
1. Выявление и исправление ошибок программы
2. Тестирование ручное или автоматизированное и обнаружение факта ошибки программистом, тестировщиком или пользователем
3. Исправление ошибок, внесение в программу новой функциональности и снова переход на пункт 1
Существуют различные стратегии отладки:
1. Программирование без ошибок программы и ручное тестирование
2. Использование методов защитного программирования (протоколирование работы программы)
3. Инструментальная отладка, с помощью встроенного отладчика
4. Совмещение различным подходов.
studfiles.net
Оптимизация компилятора - это... Что такое Оптимизация компилятора?
Оптимизация компилятора — модификация программ, выполняемая оптимизирующими компиляторами или интерпретаторами с целью повышения производительности или компактности программ без изменения их функциональности.
Оптимизация — не обязательный, но важный этап компиляции. В принципе, она может происходить неявно во время трансляции программы, но, как правило, оптимизацию программы выделяют как отдельный этап функционирования компиляторов. Компоновщики также могут выполнять часть оптимизаций, таких как удаление неиспользуемых подпрограмм или их переупорядочивание.
Различают низко- и высокоуровневую оптимизацию. Низкоуровневая оптимизация преобразовывает программу на уровне элементарных команд, например, инструкций процессоров архитектуры x86. Высокоуровневая оптимизация осуществляется на уровне структурных элементов программы, таких как модули, функции, ветвления, циклы.
Низкоуровневая оптимизация
Включает такие техники, как:
Объединение и разделение инструкций
Данный метод оптимизации состоит в замене одной или нескольких инструкций другим, но функционально эквивалентным набором, дающим выигрыш для целевой архитектуры.
Так, для конвейерной архитектуры разделение сложных инструкций на более простые может давать выигрыш в быстродействии, если несколько инструкций могут быть параллельно выполнены на конвейере. Так, например, процессор Pentium MMX быстрее выполнит набор инструкций
чем эквивалентную ему инструкцию loop метка; для ранних процессоров архитектуры x86, от 8086 до 80286, всё иначе, так как они выполняют инструкции неконвейеризированно, вследствие чего команда loop метка создаёт более экономичный и быстрый код для этих процессоров.
Высокоуровневая оптимизация
Включает такие техники, как:
- Удаление недосягаемого («мёртвого») кода и неиспользуемых присвоений
- Подгонка, обращение циклов
- Оптимизация множественных ветвлений
- Развёртка, свёртка, объединение и разделение циклов
- Вычисление инвариантов циклов, вынесение общих подвыражений и кода в ветвлениях, вынесение ветвлений из циклов
- Переключение, объединение и разделение ветвлений
- Предвыборка данных
- Переупорядочивание функций
- Встраивание и извлечение функций
Локальность оптимизации
Многие оптимизирующие компиляторы ограничивают любые действия по оптимизации подпрограммы её телом, то есть не производят межпроцедурной или полнопрограммной оптимизации.
Литература
- Касьянов В. Н. Оптимизирующие преобразования программ. — М.: Наука, 1988. — 336 с.
dic.academic.ru
Оптимизация компилятора - Википедия
Оптимизирующий компилятор — компилятор, в котором используются различные методы получения более оптимального программного кода при сохранении его функциональных возможностей. Наиболее распространённые цели оптимизации: сокращение времени выполнения программы, повышение производительности, компактификация программного кода, экономия памяти, минимизация энергозатрат, уменьшение количества операций ввода-вывода.
Оптимизация может происходить неявно во время трансляции программы, но, как правило, считается отдельным этапом работы компилятора. Компоновщики также могут выполнять часть оптимизаций, таких как удаление неиспользуемых подпрограмм или их .
Оптимизация, как правило, реализуется с помощью последовательности оптимизирующих преобразований, алгоритмов, которые принимают программу и изменяют её для получения семантически эквивалентного варианта, который более эффективен с точки зрения какого-либо набора целей оптимизации. Было показано, что некоторые проблемы оптимизации кода являются NP-полными[1], или даже неразрешимыми[2]. Тем не менее, практически многие из них решаются эвристическими методами, дающими вполне удовлетворительные результаты.
Различают низко- и высокоуровневую оптимизацию. Низкоуровневая оптимизация преобразовывает программу на уровне элементарных команд, например, инструкций процессора конкретной архитектуры. Высокоуровневая оптимизация осуществляется на уровне структурных элементов программы, таких как модули, функции, ветвления, циклы.
Типы оптимизаций[ | ]
Методы, используемые в оптимизациях, могут быть классифицированы по сфере применения: они могут влиять как на отдельный оператор, так и на целую программу. Локальные методы (затрагивающие небольшую часть программы) проще реализовать, чем глобальные (применяемые ко всей программе), но при этом глобальные методы часто оказываются более выгодными.
Peephole-оптимизация[ | ]
Локальные peephole-оптимизации (англ. peephole — «глазок») рассматривают несколько соседних (в терминах одного из графов представления программы) инструкций (как будто «смотрит в глазок» на код), чтобы увидеть, можно ли с ними произвести какую-либо трансформацию с точки зрения цели оптимизации. В частности, они могут быть заменены одной инструкцией или более короткой последовательностью инструкций.
Например, удвоение числа может быть более эффективно выполнено с использованием левого сдвига или путём сложения числа с таким же.
Локальная оптимизация[ | ]
В локальной оптимизации рассматривается только информация одного базового блока за один шаг[3]. Так как в базовых блоках нет переходов потока управления, эти оптимизации требуют незначительного анализа (экономя время и снижая требования к памяти), но это также означает, что не сохраняется информация для следующего шага.
Внутрипроцедурная оптимизация[ | ]
Внутрипроцедурные оптимизации (англ. intraprocedural — глобальные оптимизации, выполняемые целиком в рамках единицы трансляции (например, функции или процедуры))[4], при такой оптимизации задействовано гораздо больше информации, чем в локальной, что позволяет достигать более значительных эффектов, но при этом часто требуются ресурсозатратные вычисления. При наличии в оптимизируемой программной единице глобальных переменных оптимизация такого вида может быть затруднена.
Оптимизация циклов[ | ]
Существует большое количество оптимизаций, применяемых к циклам. При большом количестве повторений цикла такие оптимизации чрезвычайно эффективны, так как небольшим преобразованием влияют на значительную часть выполнения программы. Поскольку циклы — весомая часть времени выполнения многих программ, оптимизации циклов существуют практически во всех компиляторах и являются самыми важными.
Например, выявив инварианты цикла, иногда можно вынести часть операций из цикла, чтобы не выполнять избыточные повторные вычисления.
Межпроцедурная оптимизация[ | ]
Такие виды оптимизаций анализируют сразу весь исходный код программы. Бо́льшее количество информации, извлекаемой данными методами, означает что оптимизации могут быть более эффективным по сравнению с другими методами. Такие оптимизации могут использовать довольно сложные методы, например, вызов функции замещается копией тела функции (встраивание или inline).
Пример Пусть есть некоторая функция:
int pred(int x) { if (x == 0) return 0; else return x - 1encyclopaedia.bid