оптимизация программного кода это
Оптимизация программного кода
Зачем же нужна оптимизация и откуда она взялась? С первых дней развития эры вычислительной техники возник вопрос экономии места и увеличения производительности программ. Программистам приходилось создавать сложные дееспособные программы, которые смогли бы работать при очень низком быстродействии процессоров, использовать считанные килобайты оперативной памяти и места на диске. Поэтому все разработчики ПО были заинтересованы в максимальном быстродействии при минимальном размере кода.
Сегодня эти мощности вызывают улыбку. Но традиции оптимизации кода сохранились. Как известно, сколько ни наращивай размер диска и объем ОЗУ, все равно будет мало. Потому написанные «неряшливо» приложения, медленные и ресурсоемкие, проигрывают конкурентную борьбу аналогам, даже если они красивы и удобны.
Особо жесткие требования касаются драйверов и системных утилит. Они должны работать быстро, корректно и максимально экономить ресурсы компьютера. То есть взаимодействие процессора с периферией должно происходить без лишних затрат времени, с высокой скоростью передачи данных между устройствами. И сейчас мы решили немного разобраться, какие бывают способы оптимизировать программный код, в чем их плюсы и минусы.
Основные принципы оптимизации
Оптимизация стоит на трех «китах» — естественность, производительность, затраченное время. Давайте разберемся подробнее, что они означают.
Полезный совет. Перед началом оптимизации программного кода не забудьте сохранить копию исходного кода. Тогда в случае ошибки при внесении изменений всегда можно будет откатится до рабочей версии.
Стоит ли применять Ассемблер
Многие разработчики искренне считают, что критические секции (некоторые называют их «узкими» местами программы) кода удобнее писать на ассемблере, так как он обеспечивает самый быстрый доступ к данным и устройствам.
Многочисленные сравнения результатов оптимизации кода на языке высокого уровня и применения ассемблерных вставок показывают, что в первом случае после компиляции программа будет работать чуть медленнее, чем при использовании ассемблера. Обычно эти цифры измеряются от 2% до 7%. Максимум – разница составляет 20%. Стоит ли ради получения столь малого эффекта тратить время и силы на написание ассемблерной версии? На наш взгляд лучше уделить больше внимания работе с кодом, который написан на ЯП высокого уровня, и оптимизировать работу алгоритма.
Как правильно оптимизировать
Теперь давайте разберемся, как проводится оптимизация, и разберемся, с чего начинать, чего лучше избегать, и когда без ассемблера не обойтись.
Начало оптимизации
Первое, что необходимо сделать, это выявить «узкие места» программы. Нет смысла трогать тот кусок программы, где и без вас все работает прекрасно. Здесь вы вряд ли что-то выиграете при оптимизации. В первую очередь, стоит обратить внимание на блоки кода, которые регулярно или часто повторяются в процессе работы – циклы и подпрограммы.
Пример: Если оптимизировать работу цикла хотя бы на 2% за одну итерацию, а число его повторов будет 1000 раз, в итоге мы получаем: 2% × 1000 = 2000%, вполне ощутимый результат при работе кода.
Участки кода, которые не оптимизируются
Не стоит трогать единичные операнды, поскольку работают они крайне редко и толку в их модификации нет никакого. Они отработают один раз, и больше к этому коду обращений не будет. Но при условии, что при внесении изменений вы добьетесь увеличения производительности более чем на 10%, это не лишено смысла. Здесь все зависит от вашего здравого смысла и опыта.
Также вы не сумеете добиться достойных результатов в случае обращения к внешним устройствам и другим программным системам. До и после таких фрагментов можно что-то ускорить. Но там, где задержка может возникать по причине взаимодействия с внешними данными, лучше предусмотрите заглушку типа «Подождите, операция может занять несколько минут».
Еще раз об ассемблере
Помните, что использовать ЯП низкого уровня нужно только там, где это действительно необходимо. Нет никакой причины вставлять его где ни попадя, это никак не повлияет на производительность. Впрочем, если вы – асс ассемблера и можете писать на нем также быстро, как и на удобном языке высокого уровня, можете пользоваться им активно. Правда, тогда возникает другой нюанс – вы усложняете читабельность кода для программистов, которые будут заниматься проектом после вас.
Оптимизировать или нет?
Если вы не уверены, что сможете ускорить работу программы, при этом тестирование не выявляет никаких критичных проблем, оставьте все как есть. Помните старую мудрость программистов: работает – не мешай.
Иначе вы можете потратить лишнее время на работу с кодом, а в результате сделаете даже хуже, программа начнет работать медленно, да и от багов никто не застрахован.
Заниматься оптимизацией следует только тогда, когда на программу поступают жалобы пользователей либо на этапе тестирования находятся проблемные участки, на которых программа «подвисает» и тормозит работу устройства. В таких случаях производится отладка, а для уже выпущенных в серию продуктов выпускают новые версии или, так называемые, «заплатки» (patch).
Методы оптимизации программ
Оптимизация кода не слишком отличается от обычного исправления багов. Более того, с их устранения и начинается работа по оптимизации программы.
Первым делом нужно проверить код на наличие устаревших или вообще ненужных фрагментов. Таких исполняемых модулей или веток в большой программе находится обычно много. Что-то написали, но оказалось, что функционал не нужен, и его просто забыли удалить. Другие части оказались не нужны в результате очередного обновления. Все они занимают место. А некоторые продолжают исполняться, хоть в этом нет никакого смысла. И, таким образом, замедляют работу системы.
Пишите аккуратный код. Не забывайте о комментариях. Так вы поможете и себе, и другим разработчикам, понять, что в программе нужно, а что – уже не актуально. Эти общие советы помогают и при отладке, и при поиске багов. В общем, не будьте неряшливым «говнокодером», и ваши программы будут работать быстро и без проблем.
Второй этап поиска проблемных мест также простой. Разберитесь, когда приложение работает медленнее всего, в какие моменты оно заметно подвисает. Изучите код на предмет ошибок или излишне сложных запутанных решений. Попробуйте написать проще.
Если все равно что-то не работает или «тормозит», придется использовать профилировщики отладочного вывода, в том числе, с учетом записи логов всех запросов SQL, если программа работает с базами данных. В случае поиска вслепую вы потратите много времени и не факт, что сможете добиться положительных результатов.
Рассмотрим самые популярные методы оптимизации программ. Некоторые из них возможно вызовут у вас недоумение, но поверьте, они работают.
Настройка окружения
Используемая вами SQL или другая система управления базами данных могут быть неверно настроены. Настройки по умолчанию далеко не идеальны. Возможно, какие-то дополнительные проверки как раз и замедляют процесс.
Иногда удается заметно ускорить работу программы, изменив ключевые настройки виртуальной машины Java. Кстати, это поможет сделать быстрее работу не только тестируемого приложения, но и всей системы.
Также обратите внимание на саму операционную систему и мощность «железа». Может быть они вообще не предназначены для работы программного продукта, который вы пытаетесь запустить и ускорить? А, может, устарели и потому «тормозят»?
Все это не относится напрямую к оптимизации программы, но проверить нужно до начала работы с кодом. Просто потому, что такие «досадные мелочи» нередко оказываются ключевой проблемой, а код – вообще не причем. И не стоит снисходительно улыбаться. Проверять окружение забывают даже опытные разработчики.
Избавляемся от ненужного функционала
Для увеличения скорости работы приложения можно использовать подход сокращения ненужного кода. Часто бывает так, что программа автоматически решает маловажные или уже не актуальные задачи. Например, заказчик, описывая задачу программисту, попросил о каких-то возможностях, а потом передумал. Или вышел новый релиз программы, где часть функций выделили в отдельный модуль, а старый код просто забыли удалить.
В итоге мы имеем лишний функционал, который будет «тормозить» быстродействие. Со временем такой код обрастет совершенно ненужными «костылями» и «подпорками», что не лучшим образом влияет на производительность. В таком случае мы рекомендуем просто переписать модуль «с нуля», выбросив все старое, как ненужных хлам.
Мемоизация
Меморизация (от англ. Memoization) означает запоминание. Фактически это простое сохранение результата выполнения определенной функции, которое поможет избежать ее повторного выполнения. Применяя меморизацию, вы сможете повысить производительность программы.
Работает это очень просто. Перед тем, как функция будет выполняться, проверяется условие – исполнялась ли она ранее. По итогам можно получить два варианта:
Иногда говорят о табулировании, это синоним мемоизации, который используется во многих языках программирования.
Кеширование
Это метод временного хранения данных в памяти устройства пользователя. Получить доступ к такой информации можно гораздо быстрее, чем каждый раз обращаться к серверу или базам данных. С помощью кэширования значительно ускоряют работу с сайтами, онлайн-системами и т.д.
Вся необходимая информация в данном случае храниться на носителях с быстрым доступом. Это может быть выделенная часть диска или оперативная память. Программа в процессе работы использует кэш по мере необходимости, и обращается к основному хранилищу данных только если не находит их в кэше.
Распараллеливание программ
Это способ адаптации алгоритмов, которые были реализованы, как программы для компьютерных систем с параллельной архитектурой. Как правило, это относится к многопроцессорным системам.
Подробно описывать метод мы здесь не будем, так как это займет слишком много места. Кратко можно сказать так: разные вычисления одной программы выполняются одновременно в параллельных потоках. Такой подход позволяют далеко не все языки, а потому тут нередко используют внешние команды системы или ассемблер.
«Ленивые» вычисления
Ленивые (Lazy evaluation) или отложенные вычисления – стратегия, которую применяют в некоторых системах счисления. Суть метода заключается в том, что все расчеты откладываются до тех пор, пока не будет затребован их результат.
Такая стратегия позволит существенно снизить общий объем производимых вычислений, так как ненужные операции попросту не будут выполняться. Чтобы метод начал работать, нужно описать зависимости функций (операндов) друг от друга, что поможет отслеживать работу. В итоге вы получите код, который будет выполняться только в том случае, когда он действительно нужен.
Метод приближения
Приближение или аппроксимаация (от лат. proxima — ближайшая или приближе́ние) – метод замены строгого алгоритма на наиболее подходящие приближенные значения, что влечет за собой определенную потерю точности вычислений. Снижение точности экономит память и повышает скорость. Для того чтобы не использовать длинную арифметику, можно воспользоваться обычными float’ами. Но пользоваться таким методом нужно крайне осторожно, не всегда снижение точности допустимо.
Использование сторонних языков
Иногда написанная программа может медленно работать из-за того, что много времени занимает проверка описанных типов, что занимает дополнительное время. Чтобы избежать этого эффекта, можно применять фрагменты кода или модули, написанные на других языках. Но делать это нужно крайне осторожно. Все эти «лишние» проверки защищают вас от багов и «дыр» в безопасности, связанных, в том числе, с буферизацией. Потому хорошо подумайте, действительно ли экономия времени столь существенна? И если придете к выводу, что здесь это – лучшее решение, обязательно проведите особо внимательное тестирование.
Кроме того, если начать использовать в коде фрагменты других языков, это может вызвать эффект «зоопарка», что сильно снижает читабельность программы. Также имейте в виду, что метод может попросту не сработать или даже критически навредить всей программе.
Существует еще очень много методов оптимизации, как общеизвестных, так и личных находок программистов. Мы постарались рассказать, что такое оптимизация, и познакомить вас с самыми популярными методами. А как вы ускоряете работу своих приложений? Делитесь в комментариях.
Оптимизация кода
Какая бывает и зачем нужна.
Мы тут разбираемся в важных понятиях большой разработки:
Чтобы закрыть тему, поговорим об оптимизации.
Что такое оптимизация
Оптимизация кода — это когда программист берёт код из уже готовой и работающей программы и пытается его улучшить для какой-то цели.
Что пытаются улучшить:
Каждый из вариантов оптимизации отличается по исполнению, поэтому обычно выбирают что-то одно, самое важное: скорость, безопасность или стабильность. Потом следующими итерациями чинят всё остальное.
Иногда может получиться так, что в процессе оптимизации часть кода переписывается на другом языке или добавляется новый фреймворк. Со временем это может привести к тому, что программа может полностью перейти на другую библиотеку или сменить язык программирования.
Оптимизация скорости работы
Самая частая оптимизация кода — повышение скорости работы.
Например, при разработке программ для видеомонтажа важно, чтобы пользователь сразу увидел результат работы цветокоррекции. Если программа каждый раз будет надолго задумываться, а при этом где-то рядом будет более шустрый софт, со временем люди перейдут на него, а компания потеряет деньги. Чтобы этого не произошло, программисту дают задание ускорить обработку фильтра.
Примеры того, как можно добиться ускорения:
Оптимизация скорости загрузки
Это похоже на предыдущий пункт, но с одним отличием: чаще всего увеличивают видимую пользователю скорость загрузки программы, а не полноценную загрузку всего.
Этим приёмом часто пользуется компания Apple: они делают свои программы так, чтобы пользователь видел интерфейс практически сразу после запуска, но реально работать с программой можно лишь через 1–2 секунды. Дело в том, что на первое место ставится скорость отрисовки и загрузки интерфейса, а остальные модули программы загружаются на фоне, и на это тоже нужно время. Зато выглядит всё так, как будто программа запустилась моментально.
Вторая ситуация — когда действительно увеличивается скорость загрузки всей программы. Для этого разработчики используют аппаратные возможности железа, и выносят часть функций в отдельные модули. Если они понадобятся — программа их загрузит, а если нет, то и не нужно тратить на это время при запуске.
Оптимизация скорости ответа
Такой оптимизацией занимаются в высоконагруженных системах: базах данных, серверах и системах управления. В них важно как можно быстрее ответить на запрос, поэтому тут оптимизируют работу с сетевыми протоколами, форматами хранения для быстрого доступа к данным и параллельным вычислениям.
При этом такие программы могут загружаться по 20–30 секунд или даже несколько минут. Никто не ждёт от них моментальной загрузки, а вот моментальная реакция на запросы — нужна.
Оптимизация для стабильности и отказоустойчивости
Если мы пишем код для больницы, в котором в реальном времени обрабатываем жизненные показатели всех пациентов, то нам важно такое:
Короче: программу должно быть очень сложно сломать.
Это значит, что нам нужно сконцентрироваться на обработке исключений, проверках введённых значений, двойных запросах для гарантированного получения точных данных. Если такая программа падает, когда отваливается одна из баз данных, — её точно нужно оптимизировать.
Оптимизация для уменьшения объёма кода
Программы работают не только на компьютерах — ещё есть брелоки, сигнализации, умные чайники, системы контроля доступа, часы, автомобили и так далее. Внутрь этих устройств поставить жёсткий диск сложно, поэтому все программы хранятся внутри микроконтроллеров. Размер памяти там ограничен.
Или другая ситуация: у нас есть код программы для одной версии умных часов, а нам нужно эти же функции перенести на другие часы, где контроллер попроще и памяти поменьше.
В этих случаях разработчики идут на разные хитрости: применяют упаковку кода, чтобы он распаковался в оперативной памяти в момент выполнения; или упрощают код, делая его менее стабильным или отказоустойчивым, зато более компактным.
Ещё могут использовать недокументированные возможности железа, чтобы расширить место для хранения программы или её компонентов. Например, в старом телефоне могут положить часть модулей программы в область памяти, где хранятся мелодии звонков. Мелодии занимают мало места, и часть остаётся свободной — в этом случае модуль маскируется под мелодию и отправляется туда.
Что дальше
Теперь мы полностью готовы работать над своим старым кодом: поддерживать легаси, делать рефакторинг и оптимизировать разные функции. Сделаем всё по очереди, но уже в другой раз.
Оптимизация кода
Определение и свойства
Виды оптимизации
Оптимизация кода может проводиться как и вручную, программистом, так и автоматизированно. В последнем случае оптимизатор может быть как отдельным программным средством, так и быть встроенным в компилятор (т.н. оптимизирующий компилятор). Кроме того, следует отметить, что современные процессоры могут оптимизировать порядок выполнения инструкций кода.
Существуют такие понятия как высокоуровневая и низкоуровневая оптимизация. Высокоуровневые оптимизации в большинстве проводятся программистом, который, оперируя абстрактными сущностями (функциями, процедурами, классами и т.д.) и представляя себе общую модель решения задачи, может оптимизировать дизайн системы. Оптимизации на уровне элементарных структурных блоков исходного кода (циклов, ветвлений и т.д.) тоже обычно относят к высокому уровню; некоторые выделяют их в отдельный («средний») уровень (Н. Вирт?). Низкоуровневая оптимизация производится на этапе превращения исходного кода в набор машинных команд, и зачастую именно этот этап подвергается автоматизации. Впрочем, программисты на ассемблере считают, что никакая машина не превзойдет в этом хорошего программиста (при этом все согласны, что плохой программист сделает еще хуже и машины).
Выбор оптимизируемого участка
Вред и польза оптимизаций
Однако следует понимать, что многочисленные сложные оптимизации на уровне машинного кода могут сильно замедлить процесс компиляции. Причем выигрыш от них может быть чрезвычайно мал по сравнению с оптимизациями общего дизайна системы (Вирт). Также не следует забывать, что современные, «навороченные» синтаксически и семантически языки имеют множество тонкостей, и программист, который их не учитывает, может быть удивлен последствиями оптимизации.
Например, рассмотрим язык Си++ и т.н. Return-Value Optimization, суть которой в том, что компилятор может не создавать копии возвращаемого функцией временного объекта. Так как в этом случае компилятор «пропускает» копирование, этот прием также называется «Copy elision». Итак, следующий код:
может иметь несколько вариантов вывода:
Как ни странно, все три варианта являются законными, так как в стандарте языка позволяется пропускать вызов копирующего конструктора в данном случае, даже если у конструктора есть побочные эффекты (§12.8 Копирование объектов класса, пункт 15).
Таким образом, не стоит забывать проводить оптимизацию кода, по возможности применяя специализированные программные средства, но это следует делать аккуратно и с осторожностью, а иногда и приготовиться к неожиданностям от компилятора.
PVS-Studio
В статическом анализаторе PVS-Studio реализован набор диагностик, позволяющих обнаруживать некоторые ситуации, когда код может быть оптимизирован. Однако PVS-Studio, как и любой статический анализатор, не может выступать в качестве замены инструментов профилирования. Выявить узкие места могут только динамические анализаторы программ. Статические анализаторы не знают, какие входные данные получают программы и как часто выполняется тот или иной участок кода. Поэтому мы и говорим, что анализатор предлагает выполнить некоторые «микро оптимизации» кода, которые вовсе не гарантируют прироста производительности.
Несмотря на рассмотренный недостаток, анализатор PVS-Studio может выступать хорошим дополнением к инструментам профилирования. Более того, при работе с предупреждениями PVS-Studio, касающимися оптимизации, код часто становится проще и короче. Более подробно этот эффект рассмотрен в статье «Поговорим о микрооптимизациях на примере кода Tizen».
ТЕОРЕТИЧЕСКИЕ ОСНОВЫ ОПТИМИЗАЦИИ ПРОГРАММНОГО КОДА
В современном мире разработка программного обеспечения (ПО) превратилась в одну из самых дорогостоящих индустрий, и любые ошибки и недочеты в процессе его создания могут привести к нежелательным результатам. Написание запутанного кода чревато проблематичным изменением и сопровождением готового продукта. Ошибки, не выявленные в ходе тестирования ПО, приводят к снижению надежности и затягиванию сроков его внедрения. Поэтому актуальность разработки совершенного кода очень высока, так как она позволяет повысить его надежность. Очевидно, что такой код должен быть максимально оптимальным.
Примитивный, но правильный код, написанный программистом, во многих случаях может быть усовершенствован. Чаще всего причиной является то, что выбранный алгоритм, является шаблонным и не учитывает условия поставленной задачи, то есть транслирует языковые выражения вне зависимости от их смысла в определенные последовательности команд. Формальный алгоритм не различает особые случаи и не использует их выгод. Выбор такого подхода приводит к результатам, которые лишь отчасти отвечают требованиям экономии памяти и скорости выполнения.
Для того чтобы сгенерировать код, который использует имеющиеся команды и ресурсы машины с наибольшей эффективностью, должны быть использованы более сложные схемы трансляции. Они называются оптимизациями, а использующие их компиляторы – оптимизирующими компиляторами. Так же важно придерживаться правила 10/90, которое гласит, что 10% времени потраченное на планирование до начала работы, экономит 90% времени при решении поставленных задач.
Архитектурный дизайн системы особенно сильно влияет на её производительность. Однако выбор алгоритма влияет на эффективность больше, чем любой другой элемент дизайна. Более сложные алгоритмы и структуры данных могут хорошо оперировать с большим количеством элементов, в то время как простые алгоритмы подходят для небольших объёмов данных – накладные расходы на инициализацию более сложного алгоритма могут перевесить выгоду от его использования [1, c.5].
Чем больше памяти использует программа, тем быстрее она обычно выполняется. Например, сортировка ступенчатого массива обычно выполняется построчно – программа читает каждую строку, сортирует её, а затем выводит эту строку. Такая программа хорошо экономит память, т.к. использует её только для хранения одной строки, но производительность программы обычно очень плохая. Производительность может быть значительно улучшена чтением целого файла и записью потом отсортированного результата. Однако такой способ использует больше памяти. Кэширование результата также эффективно, однако требует большего количества памяти для использования.
Цель данной работы – изучить теоретические основы оптимизации программного кода.
Поставленная цель определила следующие задачи:
Рассмотреть термин «оптимизация кода» и связанные с ним понятия.
Изучить виды и подход к оптимизации кода.
Познакомиться с методиками оптимизации кода.
ТЕРМИН «ОПТИМИЗАЦИЯ КОДА» И СВЯЗАННЫЕ С НИМ ПОНЯТИЯ
Оптимизация кода – это один из способов преобразования кода, приводящий к улучшению его характеристик и повышению производительности программы. Среди целей оптимизации можно выделить уменьшение размера кода, объема используемой оперативной памяти, повышение скорости выполнения программы, уменьшение количества операций ввода – вывода. Так как под оптимизацией понимается внесение незначительных поправок, то есть изменение одного класса, одного метода или всего лишь нескольких строк кода. Поэтому какие-либо крупные изменения проекта, приводящие к повышению производительности оптимизацией не считаются.
Существует требование, которые обычно предъявляется к методу оптимизации – оптимизированная программа должна иметь тот же результат и побочные эффекты на тех же входных данных, что и неоптимизированная программа. Тем не менее, если изменения поведения программы, не имеет большого значения на фоне выигрыша за счет использования оптимизации, то данное требование может и не играть главной роли.
Кроме того, не существует универсального решения, которое подходило бы ко всем случаям, поэтому приходится использовать альтернативные решения, для оптимизации только ключевых параметров. Как правило, необходимые ресурсы для достижения требуемого результата, то есть получения полностью оптимальной программы, которую невозможно дальше улучшить, превышают выгоду, которую можно получить, затрачивая эти ресурсы. Именно поэтому оптимальные программы не создают просто потому, что некоторый процесс оптимизации может закончиться раньше. Как показывает практика, в большинстве случаев даже при этом достигаются значительные улучшения [2, c.153].
Встречаются ситуации, когда оптимизированный код вручную, оказывается менее эффективнее кода, сгенерированного компилятором.
Каждый этап от проектирования до оптимизации кода допускает существенное повышение производительности программного обеспечения [3, c. 576].
Стоит заметить, что оптимизация кода – это не самый эффективный способ повышения производительности, более того это не самый легкий способ повысить производительность: легче купить новое оборудование или компилятор с улучшенным модулем оптимизации. Так же это не самый дешевый способ: на оптимизацию кода вручную изначально уходит много времени, а потом оптимизированный код труднее сопровождать.
Однако оптимизация кода привлекательна по ряду причин. Например, ускорить выполнение метода в 10 раз путем изменения всего лишь нескольких его строк. Кроме того, овладение мастерством написания эффективного кода – признак превращение в серьезного программиста.
Оптимизация в основном фокусируется на одиночном или повторном времени выполнения, использовании памяти, дискового пространства, пропускной способности или некотором другом ресурсе. Это обычно требует компромиссов (tradeoff) – один параметр оптимизируется за счёт других. Например, увеличение размера программного кэша чего-либо улучшает производительность времени выполнения, но также увеличивает потребление памяти. Другие распространённые компромиссы включают прозрачность кода и его выразительность, почти всегда ценой деоптимизации. Сложные специализированные алгоритмы требуют больше усилий по отладке и увеличивают вероятность ошибок.
Оптимизацию производительности следует отличать от рефакторинга. Цель рефакторинга – сделать код программы более легким для понимания. Как и оптимизация, рефакторинг обычно не изменяет поведение программы. Но оптимизация часто затрудняет понимание кода, что противоположно рефакторингу.
ВИДЫ ОПТИМИЗАЦИИ ПРОГРАММНОГО КОДА
Оптимизация кода может проводиться, как и вручную, программистом, так и автоматизировано. В последнем случае оптимизатор может быть, как отдельным программным средством, так и быть встроенным в компилятор [4, c.3].
Хороший оптимизирующий компилятор может повысить быстродействие кода на 40 и более процентов, тогда как многие из методик, используемых программистом вручную, только на 15-30%.
Существуют такие понятия как высокоуровневая и низкоуровневая оптимизация. Высокоуровневые оптимизации в большинстве проводятся программистом, который, оперируя абстрактными сущностями (функциями, процедурами, классами и т.д.) и представляя себе общую модель решения задачи, может оптимизировать дизайн системы. Оптимизации на уровне элементарных структурных блоков исходного кода (циклов, ветвлений и т.д.) тоже обычно относят к высокому уровню; некоторые выделяют их в отдельный («средний») уровень (Н. Вирт). Низкоуровневая оптимизация производится на этапе превращения исходного кода в набор машинных команд, и зачастую именно этот этап подвергается автоматизации. Впрочем, программисты на ассемблере считают, что никакая машина не превзойдет в этом хорошего программиста (при этом все согласны, что плохой программист сделает еще хуже машины) [5].
Рано начатая оптимизация кода ведет к усложнению и замедлению процесса разработки, поэтому большинство советов по улучшению кода лучше применять уже на завершающей фазе разработки, когда уже все отлажено и работает.
Иногда оптимизация программы после ее написания не позволяет достичь нужных показателей производительности, из-за чего приходится вносить крупные изменения в завершенный код. Это значит, что оптимизация небольших фрагментов все равно не привела бы к нужным результатам. Проблема в таких ситуациях объясняется не низким качеством кода, а неподходящей архитектурой программы.
Подход выполнения оптимизации по мере написания кода, имеет массу недостатков:
• До создания полностью работоспособной программы найти узкие места в коде почти невозможно. Очень трудно догадаться, на какой участок кода приходится 50% времени выполнения, поэтому, оптимизируя код по мере написания, тратиться много времени на оптимизацию кода, который не нуждается в ней. А на оптимизацию по-настоящему важных участков времени не остается.
• В тех случаях, когда удается определить правильно узкие места, им уделяется слишком больше внимание, это может привести к появлению других узких мест. Если оптимизация выполняется после создания полной системы, разработчики могут определить все проблемные области и их относительную важность, что способствует эффективному распределению времени.
• Корректность, сокрытие информации, удобочитаемость становятся вторичными целями, хотя улучшить их потом сложнее, чем производительность.
Если оптимизацию нужно выполнить до создания полной программы, следует интегрировать процесс оптимизации в перспективу. Один из способов — это сделать, задать целевые показатели объема и быстродействия отдельных функций и провести оптимизация кода по мере его написания.
В некоторых проектах быстродействие или компактность кода действительно имеет большое значение. Однако таких проектов немного. В таких проектах проблемы с производительностью нужно решать путем предварительного проектирования. В остальных случаях ранняя оптимизация представляет серьезную угрозу для общего качества ПО, включая производительность.
Иногда методики оптимизации кода характеризуют как «практические правила» или приводят данные, говорящие о том, что определенный вид оптимизации обязательно приведет к желаемому результату. Однако, концепция «практических правил» плохо описывает саму оптимизацию кода. Единственным верным правилом является оценка результатов каждого вида оптимизации в конкретной среде. Важно убеждаться в том, что изменение, внесенное в код, не ухудшило работу программы в целом. Так как оно может привести к совершенно разным результатам в разных средах разработки.
ПОДХОД К ОПТИМИЗАЦИИ ПРОГРАММНОГО КОДА
Рассматривая целесообразность оптимизации кода, надо придерживаться следующего алгоритма [3, c.591]:
Написать хороший и понятный код, поддающийся легкому изменению
Если производительность не устраивает:
Сохранить работоспособную версию кода, чтобы позднее можно было вернуться к «последнему нормальному состоянию»
Оценить производительность системы с целью нахождения горячих точек
Выяснить, обусловлено ли плохое быстродействие неадекватным проектом, неверными типами данных или неудачным алгоритмами и определить, уместна ли оптимизация кода, если оптимизация кода неуместна, вернуться к п.1
Оптимизировать узкое место, определенное на этапе (с)
Оценить каждое улучшение.
Если оптимизация не привела к улучшению кода, вернуться к коду, сохраненному на этапе (а) (как правило, более чем в половине случаев попытки оптимизации будут приводить лишь к незначительному повышению производительности или к ее снижению)
Повторить процесс, начиная с п.2.
Исходя и вышесказанного, можно назвать несколько причин, по которым производительность не следует повышать путем оптимизации кода. Если программа должна быть универсальной, то нужно помнить, что методики, повышения производительности в одной среде, могут снижать ее в других. Если поменять компилятор то, возможно, новый компилятор будет автоматически выполнять те виды оптимизации и все усилия, выполненные вручную, окажутся бесполезными.
Таким образом, не стоит забывать проводить оптимизацию кода, по возможности применяя специализированные программные средства, но это следует делать аккуратно и с осторожностью, а иногда и приготовиться к неожиданностям от компилятора.
МЕТОДИКИ ОПТИМИЗАЦИИ КОДА
Не существует настолько общих методик, что бы можно было их применить для каждого кода. Однако ряд видов оптимизации кода можно, приспособить к конкретной задаче [6, c.79].
Виды оптимизации, похожи на виды рефакторинга, однако, рефакторинг направлен на улучшение внутренней структуры программы, а описанные ниже методы можно называть «антирефакторингом». Эти изменения ухудшают внутреннюю структуру программы ради повышения ее производительности. Если бы такие изменения не ухудшали внутреннюю структуру, они бы не считались видами оптимизации – использование их было бы по умолчанию и считалось бы методиками кодирования.
4.1 Логические выражения
Рассмотрим эффективное использование логических выражений.
• Прекращение проверки сразу же после получения ответа
• Использование констант корректного типа
Используйте именованные константы и литералы, имеющие тот же тип, что и переменные, которым они присваиваются. Если константа и соответствующая ей переменная имеют разные типы, перед присвоением константы переменной компилятор должен будет выполнить преобразование типа.
Чуть ниже в таблице 4.4 указаны различия во времени инициализации переменных.
Таблица – 4.4 Использование констант корректного типа
for (int j=0; j Код для цитирования: Скопировать