В информатике оптимизация программ , оптимизация кода или оптимизация программного обеспечения — это процесс модификации программной системы с целью заставить некоторые ее аспекты работать более эффективно или использовать меньше ресурсов. [1] В общем, компьютерную программу можно оптимизировать таким образом, чтобы она выполнялась быстрее или могла работать с меньшим объемом памяти или другими ресурсами, или потреблять меньше энергии.
Хотя слово «оптимизация» имеет тот же корень, что и «оптимальный», редко бывает, чтобы процесс оптимизации приводил к созданию действительно оптимальной системы. Система, как правило, может быть сделана оптимальной не в абсолютных терминах, а только относительно заданной метрики качества, которая может отличаться от других возможных метрик. В результате оптимизированная система, как правило, будет оптимальной только в одном приложении или для одной аудитории. Можно сократить время, необходимое программе для выполнения некоторой задачи, за счет того, что она будет потреблять больше памяти. В приложении, где объем памяти в дефиците, можно намеренно выбрать более медленный алгоритм , чтобы использовать меньше памяти. Часто не существует «универсального» дизайна, который хорошо работает во всех случаях, поэтому инженеры идут на компромиссы , чтобы оптимизировать атрибуты, представляющие наибольший интерес. Кроме того, усилия, необходимые для того, чтобы сделать часть программного обеспечения полностью оптимальной — неспособной к дальнейшему улучшению — почти всегда превышают разумные преимущества, которые можно было бы получить; поэтому процесс оптимизации может быть остановлен до того, как будет достигнуто полностью оптимальное решение. К счастью, зачастую самые значительные улучшения происходят на ранних этапах процесса.
Даже для заданной метрики качества (например, скорости выполнения) большинство методов оптимизации только улучшают результат; они не претендуют на получение оптимального результата. Супероптимизация — это процесс поиска действительно оптимального результата.
Оптимизация может происходить на нескольких уровнях. Обычно более высокие уровни оказывают большее влияние и их сложнее изменить на более поздних этапах проекта, что требует существенных изменений или полного переписывания, если их необходимо изменить. Таким образом, оптимизация обычно может осуществляться путем уточнения от более высокого к более низкому, при этом начальный прирост больше и достигается с меньшими усилиями, а более поздний прирост меньше и требует больше усилий. Однако в некоторых случаях общая производительность зависит от производительности очень низкоуровневых частей программы, и небольшие изменения на поздней стадии или раннее рассмотрение низкоуровневых деталей могут иметь чрезмерное влияние. Обычно некоторое внимание уделяется эффективности на протяжении всего проекта — хотя это значительно варьируется — но основная оптимизация часто считается уточнением, которое должно быть сделано поздно, если вообще когда-либо. В долгосрочных проектах обычно существуют циклы оптимизации, когда улучшение одной области выявляет ограничения в другой, и они обычно сокращаются, когда производительность приемлема или прирост становится слишком малым или дорогостоящим.
Поскольку производительность является частью спецификации программы, программа, которая недопустимо медленная, не подходит для этой цели: видеоигра с 60 Гц (кадров в секунду) приемлема, но 6 кадров в секунду неприемлемо прерывистая, производительность является соображением с самого начала, чтобы гарантировать, что система способна обеспечить достаточную производительность, и ранние прототипы должны иметь примерно приемлемую производительность, чтобы была уверенность, что окончательная система (с оптимизацией) достигнет приемлемой производительности. Это иногда опускается из-за убеждения, что оптимизацию всегда можно провести позже, что приводит к прототипным системам, которые слишком медленные — часто на порядок или больше — и системам, которые в конечном итоге оказываются неудачными, потому что они архитектурно не могут достичь своих целей производительности, например Intel 432 (1981); или тем, которым требуются годы работы для достижения приемлемой производительности, например Java (1995), которая достигла приемлемой производительности только с HotSpot (1999). Степень изменения производительности между прототипом и производственной системой, а также то, насколько она поддается оптимизации, могут быть существенным источником неопределенности и риска.
На самом высоком уровне дизайн может быть оптимизирован для наилучшего использования доступных ресурсов, заданных целей, ограничений и ожидаемого использования/нагрузки. Архитектурный дизайн системы в подавляющем большинстве влияет на ее производительность. Например, система, которая ограничена задержкой сети (где задержка сети является основным ограничением общей производительности), будет оптимизирована для минимизации сетевых поездок, в идеале делая один запрос (или не делая запросов, как в push-протоколе ), а не несколько поездок туда и обратно. Выбор дизайна зависит от целей: при проектировании компилятора , если ключевым приоритетом является быстрая компиляция, однопроходный компилятор быстрее многопроходного компилятора (при условии одинаковой работы), но если целью является скорость выходного кода, более медленный многопроходный компилятор лучше выполняет цель, даже если сам по себе он занимает больше времени. Выбор платформы и языка программирования происходит на этом уровне, и их смена часто требует полной переписывания, хотя модульная система может позволить переписать только некоторые компоненты — например, программа на Python может переписать критически важные для производительности разделы на языке C. В распределенной системе выбор архитектуры ( клиент-сервер , одноранговая сеть и т. д.) происходит на уровне проектирования и может быть трудноизменяемым, особенно если все компоненты не могут быть заменены синхронно (например, старые клиенты).
Учитывая общую конструкцию, хороший выбор эффективных алгоритмов и структур данных , а также эффективная реализация этих алгоритмов и структур данных идут следующими. После конструкции выбор алгоритмов и структур данных влияет на эффективность больше, чем любой другой аспект программы. Обычно структуры данных сложнее изменить, чем алгоритмы, поскольку предположение о структуре данных и его предположения о производительности используются на протяжении всей программы, хотя это можно минимизировать, используя абстрактные типы данных в определениях функций и ограничивая определения конкретных структур данных несколькими местами.
Для алгоритмов это в первую очередь заключается в обеспечении того, чтобы алгоритмы были постоянными O(1), логарифмическими O(log n ), линейными O( n ) или в некоторых случаях логарифмически линейными O( n log n ) на входе (как в пространстве, так и во времени). Алгоритмы с квадратичной сложностью O( n 2 ) не масштабируются, и даже линейные алгоритмы вызывают проблемы при повторном вызове и обычно заменяются постоянными или логарифмическими, если это возможно.
Помимо асимптотического порядка роста, имеют значение постоянные факторы: асимптотически более медленный алгоритм может быть быстрее или меньше (потому что проще), чем асимптотически более быстрый алгоритм, когда они оба сталкиваются с небольшими входными данными, что может иметь место в реальности. Часто гибридный алгоритм обеспечивает наилучшую производительность из-за этого компромисса, изменяющегося с размером.
Общая методика повышения производительности — избегать работы. Хорошим примером является использование быстрого пути для общих случаев, повышение производительности за счет избежания ненужной работы. Например, использование простого алгоритма макета текста для латинского текста, переключение на сложный алгоритм макета только для сложных шрифтов, таких как деванагари . Другая важная методика — кэширование, в частности мемоизация , которая позволяет избежать избыточных вычислений. Из-за важности кэширования в системе часто существует много уровней кэширования, что может вызывать проблемы из-за использования памяти и проблемы с корректностью из-за устаревших кэшей.
Помимо общих алгоритмов и их реализации на абстрактной машине, выбор конкретного уровня исходного кода может иметь существенное значение. Например, на ранних компиляторах C while(1)
был медленнее, чем for(;;)
для безусловного цикла, потому что while(1)
оценивал 1, а затем имел условный переход, который проверял, было ли это истинным, в то время как for (;;)
имел безусловный переход . Некоторые оптимизации (такие как эта) в настоящее время могут быть выполнены оптимизирующими компиляторами . Это зависит от исходного языка, целевого машинного языка и компилятора, и может быть как сложным для понимания или прогнозирования, так и меняться со временем; это ключевое место, где понимание компиляторов и машинного кода может улучшить производительность. Циклически-инвариантное перемещение кода и оптимизация возвращаемого значения являются примерами оптимизаций, которые уменьшают потребность во вспомогательных переменных и могут даже привести к более высокой производительности за счет избежания обходных оптимизаций.
Между исходным кодом и компиляцией директивы и флаги сборки могут использоваться для настройки параметров производительности в исходном коде и компиляторе соответственно, например, с помощью определений препроцессора для отключения ненужных функций программного обеспечения, оптимизации для конкретных моделей процессоров или аппаратных возможностей или прогнозирования ветвления , например. Системы распространения программного обеспечения на основе исходного кода, такие как BSD Ports и Gentoo Portage , могут использовать преимущества этой формы оптимизации.
Использование оптимизирующего компилятора , как правило, гарантирует, что исполняемая программа оптимизирована по крайней мере настолько, насколько это может предсказать компилятор.
На самом низком уровне написание кода с использованием языка ассемблера , разработанного для конкретной аппаратной платформы, может производить наиболее эффективный и компактный код, если программист использует полный репертуар машинных инструкций . Многие операционные системы , используемые на встроенных системах, традиционно писались на ассемблерном коде по этой причине. Программы (кроме очень маленьких программ) редко пишутся от начала до конца на ассемблере из-за требуемого времени и стоимости. Большинство из них компилируются с языка высокого уровня на ассемблер и вручную оптимизируются оттуда. Когда эффективность и размер менее важны, большие части могут быть написаны на языке высокого уровня.
С появлением современных оптимизирующих компиляторов и большей сложностью современных процессоров становится все сложнее писать более эффективный код, чем тот, который генерирует компилятор, и лишь немногим проектам требуется этот «окончательный» этап оптимизации.
Большая часть кода, написанного сегодня, предназначена для запуска на максимально возможном количестве машин. Как следствие, программисты и компиляторы не всегда используют преимущества более эффективных инструкций, предоставляемых новыми процессорами, или особенности старых моделей. Кроме того, ассемблерный код, настроенный для конкретного процессора без использования таких инструкций, может все еще быть неоптимальным на другом процессоре, ожидая другой настройки кода.
Сегодня программисты, как правило, вместо того, чтобы писать на языке ассемблера, используют дизассемблер для анализа выходных данных компилятора и изменения высокоуровневого исходного кода, чтобы его можно было скомпилировать более эффективно или понять, почему он неэффективен.
Компиляторы Just-in-time могут создавать настраиваемый машинный код на основе данных времени выполнения за счет накладных расходов на компиляцию. Эта техника восходит к самым ранним движкам регулярных выражений и получила широкое распространение с Java HotSpot и V8 для JavaScript. В некоторых случаях адаптивная оптимизация может выполнять оптимизацию времени выполнения , превосходящую возможности статических компиляторов, путем динамической настройки параметров в соответствии с фактическими входными данными или другими факторами.
Профильная оптимизация — это метод оптимизации компиляции с опережением времени (AOT), основанный на профилях времени выполнения, и он похож на статический аналог «среднего случая» динамического метода адаптивной оптимизации.
Самоизменяющийся код может изменять себя в ответ на условия времени выполнения с целью оптимизации кода; это чаще встречается в программах на языке ассемблера.
Некоторые конструкции ЦП могут выполнять некоторые оптимизации во время выполнения. Некоторые примеры включают внеочередное выполнение , спекулятивное выполнение , конвейеры инструкций и предикторы ветвлений . Компиляторы могут помочь программе использовать преимущества этих функций ЦП, например, с помощью планирования инструкций .
Оптимизацию кода можно также в целом разделить на платформенно -зависимые и платформенно-независимые методы. В то время как последние эффективны на большинстве или всех платформах, платформенно-зависимые методы используют определенные свойства одной платформы или полагаются на параметры в зависимости от одной платформы или даже от одного процессора. Поэтому может потребоваться написание или создание разных версий одного и того же кода для разных процессоров. Например, в случае оптимизации на уровне компиляции платформенно-независимые методы являются общими методами (такими как развертывание цикла , сокращение вызовов функций, эффективные процедуры с точки зрения памяти, сокращение условий и т. д.), которые влияют на большинство архитектур ЦП аналогичным образом. Отличный пример платформенно-независимой оптимизации был показан с внутренним циклом for, где было замечено, что цикл с внутренним циклом for выполняет больше вычислений в единицу времени, чем цикл без него или цикл с внутренним циклом while. [2] Как правило, они служат для сокращения общей длины пути инструкций, необходимой для завершения программы, и/или сокращения общего использования памяти во время процесса. С другой стороны, платформенно-зависимые методы включают планирование инструкций, параллелизм на уровне инструкций , параллелизм на уровне данных, методы оптимизации кэша (т. е. параметры, которые различаются на разных платформах), а оптимальное планирование инструкций может быть разным даже на разных процессорах одной и той же архитектуры.
Вычислительные задачи могут быть выполнены несколькими различными способами с различной эффективностью. Более эффективная версия с эквивалентной функциональностью известна как сокращение силы . Например, рассмотрим следующий фрагмент кода C , целью которого является получение суммы всех целых чисел от 1 до N :
int i , сумма = 0 ; for ( i = 1 ; i <= N ; ++ i ) { сумма += i ; } printf ( "сумма: %d \n " , сумма );
Этот код можно (при условии отсутствия арифметического переполнения ) переписать с использованием математической формулы следующего вида:
int sum = N * ( 1 + N ) / 2 ; printf ( "sum: %d \n " , sum );
Оптимизация, иногда выполняемая автоматически оптимизирующим компилятором, заключается в выборе метода ( алгоритма ), который является более эффективным с вычислительной точки зрения, сохраняя при этом ту же функциональность. См. раздел Алгоритмическая эффективность для обсуждения некоторых из этих методов. Однако значительного улучшения производительности часто можно добиться, удалив лишнюю функциональность.
Оптимизация не всегда является очевидным или интуитивным процессом. В приведенном выше примере «оптимизированная» версия может быть на самом деле медленнее исходной версии, если N будет достаточно малым, а конкретное оборудование окажется намного быстрее при выполнении операций сложения и циклов , чем умножения и деления.
Однако в некоторых случаях оптимизация опирается на использование более сложных алгоритмов, использование «особых случаев» и особых «трюков» и выполнение сложных компромиссов. «Полностью оптимизированная» программа может быть более трудной для понимания и, следовательно, может содержать больше ошибок , чем неоптимизированные версии. Помимо устранения очевидных антишаблонов, некоторые оптимизации на уровне кода снижают поддерживаемость.
Оптимизация обычно фокусируется на улучшении только одного или двух аспектов производительности: времени выполнения, использования памяти, дискового пространства, пропускной способности, энергопотребления или какого-либо другого ресурса. Обычно это требует компромисса — когда один фактор оптимизируется за счет других. Например, увеличение размера кэша повышает производительность во время выполнения, но также увеличивает потребление памяти. Другие распространенные компромиссы включают ясность и краткость кода.
Бывают случаи, когда программист, выполняющий оптимизацию, должен решить улучшить программное обеспечение для некоторых операций, но за счет снижения эффективности других операций. Эти компромиссы иногда могут иметь нетехнический характер — например, когда конкурент опубликовал результат эталонного теста , который необходимо превзойти для улучшения коммерческого успеха, но, возможно, это сопряжено с бременем снижения эффективности обычного использования программного обеспечения. Такие изменения иногда в шутку называют пессимизациями .
Оптимизация может включать в себя поиск узкого места в системе — компонента, который является ограничивающим фактором производительности. С точки зрения кода, это часто будет горячая точка — критическая часть кода, которая является основным потребителем необходимого ресурса — хотя это может быть и другой фактор, такой как задержка ввода-вывода или пропускная способность сети.
В информатике потребление ресурсов часто подчиняется степенному закону распределения, а принцип Парето можно применить к оптимизации ресурсов, заметив, что 80% ресурсов обычно используются 20% операций. [3] В программной инженерии часто более точным приближением является то, что 90% времени выполнения компьютерной программы тратится на выполнение 10% кода (в данном контексте это известно как закон 90/10).
Более сложные алгоритмы и структуры данных хорошо работают со многими элементами, в то время как простые алгоритмы больше подходят для небольших объемов данных — настройка, время инициализации и постоянные факторы более сложного алгоритма могут перевесить выгоду, и, таким образом, гибридный алгоритм или адаптивный алгоритм может быть быстрее, чем любой одиночный алгоритм. Профилировщик производительности может использоваться для сужения решений о том, какая функциональность соответствует каким условиям. [4]
В некоторых случаях добавление дополнительной памяти может помочь ускорить работу программы. Например, программа фильтрации обычно считывает каждую строку, фильтрует и выводит ее немедленно. Это использует достаточно памяти только для одной строки, но производительность обычно низкая из-за задержки каждого чтения с диска. Кэширование результата также эффективно, хотя также требует большего использования памяти.
Оптимизация может снизить читаемость и добавить код, который используется только для повышения производительности . Это может усложнить программы или системы, затруднив их поддержку и отладку. В результате оптимизация или настройка производительности часто выполняется в конце этапа разработки .
Дональд Кнут сделал следующие два заявления об оптимизации:
«Мы должны забыть о небольших показателях эффективности, скажем, в 97% случаев: преждевременная оптимизация — корень всех зол. Но мы не должны упускать наши возможности в этих критических 3%» [5]
( Несколько лет спустя он также приписал эту цитату Тони Хоару [6] , хотя это могло быть ошибкой, поскольку Хоар отрицает, что придумал эту фразу. [7] )
«В устоявшихся инженерных дисциплинах легко достижимое улучшение на 12% никогда не считается незначительным, и я считаю, что та же точка зрения должна преобладать в программной инженерии» [5]
"Преждевременная оптимизация" — это фраза, используемая для описания ситуации, когда программист позволяет соображениям производительности влиять на дизайн фрагмента кода. Это может привести к тому, что дизайн будет не таким чистым, каким он мог бы быть, или код будет некорректным, поскольку код усложняется оптимизацией, а программист отвлекается на оптимизацию.
Принимая решение об оптимизации определенной части программы, всегда следует учитывать закон Амдаля : влияние на всю программу во многом зависит от того, сколько времени фактически тратится на эту конкретную часть, что не всегда ясно при взгляде на код без анализа производительности .
Поэтому лучшим подходом будет сначала спроектировать, закодировать из дизайна, а затем профилировать / протестировать полученный код, чтобы увидеть, какие части следует оптимизировать. Простой и элегантный дизайн часто легче оптимизировать на этом этапе, а профилирование может выявить неожиданные проблемы с производительностью, которые не были бы решены преждевременной оптимизацией.
На практике часто бывает необходимо учитывать цели производительности при первоначальном проектировании программного обеспечения, но программист должен сбалансировать цели проектирования и оптимизации.
Современные компиляторы и операционные системы настолько эффективны, что предполагаемое повышение производительности часто не материализуется. Например, кэширование данных на уровне приложения, которые снова кэшируются на уровне операционной системы, не приводит к улучшению выполнения. Тем не менее, это редкий случай, когда программист удалит неудачные оптимизации из производственного кода. Также верно, что прогресс в аппаратном обеспечении чаще всего устраняет любые потенциальные улучшения, однако скрывающий код будет сохраняться в будущем еще долго после того, как его цель будет сведена на нет.
Оптимизация при разработке кода с использованием макросов принимает разные формы в разных языках.
В некоторых процедурных языках, таких как C и C++ , макросы реализованы с использованием подстановки токенов. В настоящее время встроенные функции могут использоваться в качестве типобезопасной альтернативы во многих случаях. В обоих случаях тело встроенной функции может затем подвергаться дальнейшей оптимизации во время компиляции компилятором, включая сворачивание констант , что может перенести некоторые вычисления на время компиляции.
Во многих функциональных языках программирования макросы реализованы с использованием замены деревьев разбора/абстрактных синтаксических деревьев во время синтаксического анализа, что, как утверждается, делает их более безопасными в использовании. Поскольку во многих случаях используется интерпретация, это один из способов гарантировать, что такие вычисления выполняются только во время синтаксического анализа, а иногда и единственный способ.
Lisp создал этот стиль макросов, [ требуется ссылка ] и такие макросы часто называют «макросами, подобными Lisp». Похожий эффект может быть достигнут с помощью шаблонного метапрограммирования в C++ .
В обоих случаях работа переносится на время компиляции. Разница между макросами C, с одной стороны, и макросами типа Lisp и метапрограммированием шаблонов C++ , с другой стороны, заключается в том, что последние инструменты позволяют выполнять произвольные вычисления во время компиляции/анализа, тогда как расширение макросов C не выполняет никаких вычислений и полагается на способность оптимизатора выполнять их. Кроме того, макросы C напрямую не поддерживают рекурсию или итерацию , поэтому не являются полными по Тьюрингу .
Однако, как и в случае любой оптимизации, зачастую трудно предсказать, где такие инструменты окажут наибольшее влияние до завершения проекта.
См. также Категория:Оптимизация компилятора
Оптимизация может быть автоматизирована компиляторами или выполнена программистами. Выигрыш обычно ограничен для локальной оптимизации и больше для глобальной оптимизации. Обычно самая мощная оптимизация заключается в поиске лучшего алгоритма .
Оптимизация всей системы обычно выполняется программистами, поскольку она слишком сложна для автоматизированных оптимизаторов. В этой ситуации программисты или системные администраторы явно изменяют код, чтобы вся система работала лучше. Хотя это может обеспечить большую эффективность, это намного дороже автоматизированной оптимизации. Поскольку на производительность программы влияет множество параметров, пространство оптимизации программы велико. Мета-эвристика и машинное обучение используются для решения проблемы сложности оптимизации программы. [8]
Используйте профилировщик (или анализатор производительности ), чтобы найти разделы программы, которые потребляют больше всего ресурсов — узкое место . Программисты иногда считают, что у них есть четкое представление о том, где находится узкое место, но интуиция часто ошибается. [ необходима цитата ] Оптимизация неважного фрагмента кода обычно мало что делает для улучшения общей производительности.
Когда узкое место локализовано, оптимизация обычно начинается с переосмысления алгоритма, используемого в программе. Чаще всего конкретный алгоритм может быть специально адаптирован к конкретной проблеме, обеспечивая лучшую производительность, чем общий алгоритм. Например, задача сортировки огромного списка элементов обычно выполняется с помощью процедуры быстрой сортировки , которая является одним из самых эффективных общих алгоритмов. Но если какая-то характеристика элементов может быть использована (например, они уже расположены в определенном порядке), можно использовать другой метод или даже специальную процедуру сортировки.
После того, как программист достаточно уверен, что выбран наилучший алгоритм, можно начинать оптимизацию кода. Циклы можно разворачивать (для снижения накладных расходов на циклы, хотя это часто может привести к снижению скорости, если перегружает кэш ЦП ), можно использовать как можно меньшие типы данных, вместо арифметики с плавающей точкой можно использовать целочисленную арифметику и т. д. (См. статью об алгоритмической эффективности для этих и других методов.)
Узкие места производительности могут быть вызваны языковыми ограничениями, а не алгоритмами или структурами данных, используемыми в программе. Иногда критически важная часть программы может быть переписана на другом языке программирования , который обеспечивает более прямой доступ к базовой машине. Например, для языков очень высокого уровня, таких как Python, обычно модули пишутся на языке C для большей скорости. Программы, уже написанные на языке C, могут иметь модули, написанные на ассемблере . Программы, написанные на языке D, могут использовать встроенный ассемблер .
Переписывание разделов «окупается» в таких обстоятельствах из-за общего « правила большого пальца », известного как закон 90/10, который гласит, что 90% времени тратится на 10% кода, и только 10% времени на оставшиеся 90% кода. Таким образом, приложение интеллектуальных усилий к оптимизации даже небольшой части программы может оказать огромное влияние на общую скорость — если правильные части могут быть найдены.
Ручная оптимизация иногда имеет побочный эффект в виде ухудшения читаемости. Поэтому оптимизации кода должны быть тщательно документированы (предпочтительно с использованием встроенных комментариев), а их влияние на будущую разработку должно быть оценено.
Программа, которая выполняет автоматическую оптимизацию, называется оптимизатором . Большинство оптимизаторов встроены в компиляторы и работают во время компиляции. Оптимизаторы часто могут подгонять сгенерированный код под конкретные процессоры.
Сегодня автоматизированные оптимизации почти исключительно ограничиваются оптимизацией компилятора . Однако, поскольку оптимизации компилятора обычно ограничены фиксированным набором довольно общих оптимизаций, существует значительный спрос на оптимизаторы, которые могут принимать описания проблем и оптимизаций, специфичных для языка, позволяя инженеру указывать пользовательские оптимизации. Инструменты, которые принимают описания оптимизаций, называются системами преобразования программ и начинают применяться к реальным программным системам, таким как C++.
Некоторые языки высокого уровня ( Eiffel , Esterel ) оптимизируют свои программы, используя промежуточный язык .
Распределенные вычисления или сетевые вычисления направлены на оптимизацию всей системы путем перемещения задач с компьютеров с высокой загрузкой на компьютеры с более низким временем простоя.
Иногда проблемой может стать время, необходимое для проведения оптимизации.
Оптимизация существующего кода обычно не добавляет новых функций, и, что еще хуже, она может добавить новые ошибки в ранее работающий код (как и любое изменение). Поскольку вручную оптимизированный код иногда может иметь меньшую «читаемость», чем неоптимизированный код, оптимизация может также повлиять на его поддерживаемость. Оптимизация имеет свою цену, и важно быть уверенным, что инвестиции окупятся.
Автоматический оптимизатор (или оптимизирующий компилятор , программа, которая выполняет оптимизацию кода) может сам нуждаться в оптимизации, либо для дальнейшего повышения эффективности своих целевых программ, либо для ускорения собственной работы. Компиляция, выполняемая с «включенной» оптимизацией, обычно занимает больше времени, хотя это обычно является проблемой только тогда, когда программы довольно большие.
В частности, для компиляторов JIT производительность компонента компиляции во время выполнения , выполняемого вместе с целевым кодом, является ключом к повышению общей скорости выполнения.
Однако Хоар не утверждал этого, когда я спросил его в январе 2004 г.