Некоторые приемы оптимизации программ
Приступая к выполнению нового проекта, программист должен иметь в виду, что главная цель – создание такой программы, которая работает правильно, т.е. сообщает пользователю решение той задачи, которая интересует заказчика. Если цель достигнута, можно считать, что проект выполнен успешно. Но иногда оказывается, что решение поставленной задачи получается слишком дорогой ценой.
Если речь идет, например, о расчете характеристик новой модели автомобиля, то для получения набора расчетных характеристик могут потребоваться часы, а то и сутки процессорного времени. С одной стороны, это тормозит работу по проектированию новой модели, а с другой стороны – процессорное время часто является оплачиваемым ресурсом, стоимость которого может быть достаточно большой. При работе с базами данных может оказаться, что время доступа к требуемой записи слишком велико и т.д. В этом случае программисту приходится заниматься оптимизацией задачи.
Оптимизацией называется такое преобразование исходного текста программы, при котором результат ее выполнения остается неизменным, но улучшаются ее некоторые характеристики. Эти характеристики зависят от выбранных критериев оптимизации. Основными критериями оптимизации являются:
АЛГОРИТМЫ в ПРОГРАММИРОВАНИИ для новичков | Левенштейн, Фибоначчи, Факториал и т.д.
время выполнения программы;
затраты оперативной памяти;
размер исходного кода.
При оптимизации программы необходимо выявить те фрагменты исходного текста, которые являются «основными потребителями» ресурса, а затем перепрограммировать эти фрагменты, решая задачу оптимизации.
Обратимся к проблеме оптимизации программы по затратам процессорного времени. Такая оптимизация особенно важна для расчетных программ, в которых большой удельный вес имеют математические вычисления.
Оптимизация, не зависящая от компилятора
Современные компиляторы, т.е. программы, создающие на основе исходного текста программы исполняемый код, как правило, выполняют и оптимизацию этого кода, размещают инструкции процессору таким образом, чтобы увеличить скорость выполнения программы. Вмешательство программиста в этот процесс не требуется. Следует иметь в виду, что оптимизация компилятором выполняется достаточно осторожно и возможности такой автоматической оптимизации ограничены.
Перечислим некоторые приемы, которые может использовать программист для написания исходного текста программы.
инициализация объектов данных
Во многих программах какую-то часть объектов данных необходимо инициализировать, т.е. присваивать им начальные значения. Такое присваивание выполняется либо в самом начале программы, либо, например, в начале цикла. Правильная инициализация объектов данных позволяет сэкономить драгоценное процессорное время. Так, например, если речь идет об инициализации массивов, использование цикла, скорее всего, будет менее эффективным, чем объявление этого массива типизированной константой.
программирование арифметических операций
В том случае, когда значительная часть работы программиста отводится арифметическим вычислениям, немалые резервы повышения скорости работы программы таятся в правильном программировании арифметических (и логических) выражений. Важно понимать, что различные арифметические операции значительно различаются по быстродействию.
ЭТИ ПРОГРАММЫ УСКОРЯТ ТВОЙ ПК ДО ПРЕДЕЛА!!! | Windows 10 | Windows 7
Самыми быстрыми являются операции сложения и вычитания. Более медленным является умножение, затем идет деление. Относительно много времени тратится на обращение к подпрограммам. Быстродействие также зависит и от типа операндов.
Источник: studfile.net
X Международная студенческая научная конференция Студенческий научный форум — 2018
В современном мире разработка программного обеспечения (ПО) превратилась в одну из самых дорогостоящих индустрий, и любые ошибки и недочеты в процессе его создания могут привести к нежелательным результатам. Написание запутанного кода чревато проблематичным изменением и сопровождением готового продукта. Ошибки, не выявленные в ходе тестирования ПО, приводят к снижению надежности и затягиванию сроков его внедрения. Поэтому актуальность разработки совершенного кода очень высока, так как она позволяет повысить его надежность. Очевидно, что такой код должен быть максимально оптимальным.
Примитивный, но правильный код, написанный программистом, во многих случаях может быть усовершенствован. Чаще всего причиной является то, что выбранный алгоритм, является шаблонным и не учитывает условия поставленной задачи, то есть транслирует языковые выражения вне зависимости от их смысла в определенные последовательности команд. Формальный алгоритм не различает особые случаи и не использует их выгод. Выбор такого подхода приводит к результатам, которые лишь отчасти отвечают требованиям экономии памяти и скорости выполнения.
Для того чтобы сгенерировать код, который использует имеющиеся команды и ресурсы машины с наибольшей эффективностью, должны быть использованы более сложные схемы трансляции. Они называются оптимизациями, а использующие их компиляторы – оптимизирующими компиляторами. Так же важно придерживаться правила 10/90, которое гласит, что 10% времени потраченное на планирование до начала работы, экономит 90% времени при решении поставленных задач.
Архитектурный дизайн системы особенно сильно влияет на её производительность. Однако выбор алгоритма влияет на эффективность больше, чем любой другой элемент дизайна. Более сложные алгоритмы и структуры данных могут хорошо оперировать с большим количеством элементов, в то время как простые алгоритмы подходят для небольших объёмов данных – накладные расходы на инициализацию более сложного алгоритма могут перевесить выгоду от его использования [1, c.5].
Чем больше памяти использует программа, тем быстрее она обычно выполняется. Например, сортировка ступенчатого массива обычно выполняется построчно – программа читает каждую строку, сортирует её, а затем выводит эту строку. Такая программа хорошо экономит память, т.к. использует её только для хранения одной строки, но производительность программы обычно очень плохая.
Производительность может быть значительно улучшена чтением целого файла и записью потом отсортированного результата. Однако такой способ использует больше памяти. Кэширование результата также эффективно, однако требует большего количества памяти для использования.
Цель данной работы – изучить теоретические основы оптимизации программного кода.
Поставленная цель определила следующие задачи:
Рассмотреть термин «оптимизация кода» и связанные с ним понятия.
Изучить виды и подход к оптимизации кода.
Познакомиться с методиками оптимизации кода.
ТЕРМИН «ОПТИМИЗАЦИЯ КОДА» И СВЯЗАННЫЕ С НИМ ПОНЯТИЯ
Оптимизация кода – это один из способов преобразования кода, приводящий к улучшению его характеристик и повышению производительности программы. Среди целей оптимизации можно выделить уменьшение размера кода, объема используемой оперативной памяти, повышение скорости выполнения программы, уменьшение количества операций ввода – вывода. Так как под оптимизацией понимается внесение незначительных поправок, то есть изменение одного класса, одного метода или всего лишь нескольких строк кода. Поэтому какие-либо крупные изменения проекта, приводящие к повышению производительности оптимизацией не считаются.
Существует требование, которые обычно предъявляется к методу оптимизации – оптимизированная программа должна иметь тот же результат и побочные эффекты на тех же входных данных, что и неоптимизированная программа. Тем не менее, если изменения поведения программы, не имеет большого значения на фоне выигрыша за счет использования оптимизации, то данное требование может и не играть главной роли.
Кроме того, не существует универсального решения, которое подходило бы ко всем случаям, поэтому приходится использовать альтернативные решения, для оптимизации только ключевых параметров. Как правило, необходимые ресурсы для достижения требуемого результата, то есть получения полностью оптимальной программы, которую невозможно дальше улучшить, превышают выгоду, которую можно получить, затрачивая эти ресурсы. Именно поэтому оптимальные программы не создают просто потому, что некоторый процесс оптимизации может закончиться раньше. Как показывает практика, в большинстве случаев даже при этом достигаются значительные улучшения [2, c.153].
Встречаются ситуации, когда оптимизированный код вручную, оказывается менее эффективнее кода, сгенерированного компилятором.
Каждый этап от проектирования до оптимизации кода допускает существенное повышение производительности программного обеспечения [3, c. 576].
Стоит заметить, что оптимизация кода – это не самый эффективный способ повышения производительности, более того это не самый легкий способ повысить производительность: легче купить новое оборудование или компилятор с улучшенным модулем оптимизации. Так же это не самый дешевый способ: на оптимизацию кода вручную изначально уходит много времени, а потом оптимизированный код труднее сопровождать.
Однако оптимизация кода привлекательна по ряду причин. Например, ускорить выполнение метода в 10 раз путем изменения всего лишь нескольких его строк. Кроме того, овладение мастерством написания эффективного кода – признак превращение в серьезного программиста.
Оптимизация в основном фокусируется на одиночном или повторном времени выполнения, использовании памяти, дискового пространства, пропускной способности или некотором другом ресурсе. Это обычно требует компромиссов (tradeoff) – один параметр оптимизируется за счёт других. Например, увеличение размера программного кэша чего-либо улучшает производительность времени выполнения, но также увеличивает потребление памяти. Другие распространённые компромиссы включают прозрачность кода и его выразительность, почти всегда ценой деоптимизации. Сложные специализированные алгоритмы требуют больше усилий по отладке и увеличивают вероятность ошибок.
Оптимизацию производительности следует отличать от рефакторинга. Цель рефакторинга – сделать код программы более легким для понимания. Как и оптимизация, рефакторинг обычно не изменяет поведение программы. Но оптимизация часто затрудняет понимание кода, что противоположно рефакторингу.
ВИДЫ ОПТИМИЗАЦИИ ПРОГРАММНОГО КОДА
Оптимизация кода может проводиться, как и вручную, программистом, так и автоматизировано. В последнем случае оптимизатор может быть, как отдельным программным средством, так и быть встроенным в компилятор [4, c.3].
Хороший оптимизирующий компилятор может повысить быстродействие кода на 40 и более процентов, тогда как многие из методик, используемых программистом вручную, только на 15-30%.
Существуют такие понятия как высокоуровневая и низкоуровневая оптимизация. Высокоуровневые оптимизации в большинстве проводятся программистом, который, оперируя абстрактными сущностями (функциями, процедурами, классами и т.д.) и представляя себе общую модель решения задачи, может оптимизировать дизайн системы.
Оптимизации на уровне элементарных структурных блоков исходного кода (циклов, ветвлений и т.д.) тоже обычно относят к высокому уровню; некоторые выделяют их в отдельный («средний») уровень (Н. Вирт). Низкоуровневая оптимизация производится на этапе превращения исходного кода в набор машинных команд, и зачастую именно этот этап подвергается автоматизации. Впрочем, программисты на ассемблере считают, что никакая машина не превзойдет в этом хорошего программиста (при этом все согласны, что плохой программист сделает еще хуже машины) [5].
При оптимизации кода вручную существует проблема: нужно знать не только, каким образом проводить оптимизацию, но и в каком месте её применить. Обычно из-за разных факторов (медленные операции ввода, разница в скорости работы человека-оператора и машины и т.д.) лишь 10% кода занимают целых 90% времени выполнения. Так как на оптимизацию придется расходовать дополнительное время, то вместо попыток оптимизации всей программы лучше будет оптимизировать эти «критичные» ко времени выполнения 10%. Такой фрагмент кода называют узким местом или «бутылочным горлышком» (bottleneck), и для его определения используют специальные программы — профайлеры, которые позволяют замерять время работы различных частей программы [4, c.5].
Рано начатая оптимизация кода ведет к усложнению и замедлению процесса разработки, поэтому большинство советов по улучшению кода лучше применять уже на завершающей фазе разработки, когда уже все отлажено и работает.
Главный недостаток преждевременной оптимизации — отсутствие перспективы. Это сказывается на быстродействии итогового кода, других, еще более важных атрибутах производительности и качестве программы. Если время, сэкономленное благодаря реализации наиболее простой программы, посвятит ее последующей оптимизации, итоговая программа непременно будет работать быстрее, чем программа, разработанная с использованием неорганизационного подхода к оптимизации.
Иногда оптимизация программы после ее написания не позволяет достичь нужных показателей производительности, из-за чего приходится вносить крупные изменения в завершенный код. Это значит, что оптимизация небольших фрагментов все равно не привела бы к нужным результатам. Проблема в таких ситуациях объясняется не низким качеством кода, а неподходящей архитектурой программы.
Подход выполнения оптимизации по мере написания кода, имеет массу недостатков:
• До создания полностью работоспособной программы найти узкие места в коде почти невозможно. Очень трудно догадаться, на какой участок кода приходится 50% времени выполнения, поэтому, оптимизируя код по мере написания, тратиться много времени на оптимизацию кода, который не нуждается в ней. А на оптимизацию по-настоящему важных участков времени не остается.
• В тех случаях, когда удается определить правильно узкие места, им уделяется слишком больше внимание, это может привести к появлению других узких мест. Если оптимизация выполняется после создания полной системы, разработчики могут определить все проблемные области и их относительную важность, что способствует эффективному распределению времени.
• Корректность, сокрытие информации, удобочитаемость становятся вторичными целями, хотя улучшить их потом сложнее, чем производительность.
Если оптимизацию нужно выполнить до создания полной программы, следует интегрировать процесс оптимизации в перспективу. Один из способов — это сделать, задать целевые показатели объема и быстродействия отдельных функций и провести оптимизация кода по мере его написания.
В некоторых проектах быстродействие или компактность кода действительно имеет большое значение. Однако таких проектов немного. В таких проектах проблемы с производительностью нужно решать путем предварительного проектирования. В остальных случаях ранняя оптимизация представляет серьезную угрозу для общего качества ПО, включая производительность.
Иногда методики оптимизации кода характеризуют как «практические правила» или приводят данные, говорящие о том, что определенный вид оптимизации обязательно приведет к желаемому результату. Однако, концепция «практических правил» плохо описывает саму оптимизацию кода. Единственным верным правилом является оценка результатов каждого вида оптимизации в конкретной среде. Важно убеждаться в том, что изменение, внесенное в код, не ухудшило работу программы в целом. Так как оно может привести к совершенно разным результатам в разных средах разработки.
ПОДХОД К ОПТИМИЗАЦИИ ПРОГРАММНОГО КОДА
Рассматривая целесообразность оптимизации кода, надо придерживаться следующего алгоритма [3, c.591]:
Написать хороший и понятный код, поддающийся легкому изменению
Если производительность не устраивает:
Сохранить работоспособную версию кода, чтобы позднее можно было вернуться к «последнему нормальному состоянию»
Оценить производительность системы с целью нахождения горячих точек
Выяснить, обусловлено ли плохое быстродействие неадекватным проектом, неверными типами данных или неудачным алгоритмами и определить, уместна ли оптимизация кода, если оптимизация кода неуместна, вернуться к п.1
Оптимизировать узкое место, определенное на этапе (с)
Оценить каждое улучшение.
Если оптимизация не привела к улучшению кода, вернуться к коду, сохраненному на этапе (а) (как правило, более чем в половине случаев попытки оптимизации будут приводить лишь к незначительному повышению производительности или к ее снижению)
Повторить процесс, начиная с п.2.
Исходя и вышесказанного, можно назвать несколько причин, по которым производительность не следует повышать путем оптимизации кода. Если программа должна быть универсальной, то нужно помнить, что методики, повышения производительности в одной среде, могут снижать ее в других. Если поменять компилятор то, возможно, новый компилятор будет автоматически выполнять те виды оптимизации и все усилия, выполненные вручную, окажутся бесполезными.
Таким образом, не стоит забывать проводить оптимизацию кода, по возможности применяя специализированные программные средства, но это следует делать аккуратно и с осторожностью, а иногда и приготовиться к неожиданностям от компилятора.
МЕТОДИКИ ОПТИМИЗАЦИИ КОДА
Не существует настолько общих методик, что бы можно было их применить для каждого кода. Однако ряд видов оптимизации кода можно, приспособить к конкретной задаче [6, c.79].
Виды оптимизации, похожи на виды рефакторинга, однако, рефакторинг направлен на улучшение внутренней структуры программы, а описанные ниже методы можно называть «антирефакторингом». Эти изменения ухудшают внутреннюю структуру программы ради повышения ее производительности. Если бы такие изменения не ухудшали внутреннюю структуру, они бы не считались видами оптимизации – использование их было бы по умолчанию и считалось бы методиками кодирования.
4.1 Логические выражения
Рассмотрим эффективное использование логических выражений.
• Прекращение проверки сразу же после получения ответа
Если y окажется меньше 5, то вторую проверку выполнять не нужно.
Некоторые языки поддерживают так называемую «сокращенную оценку выражений», при которой компилятор генерирует код, автоматически прекращающий проверку после получения ответа.
Если выбранный язык не поддерживает сокращенную оценку, нужно избегать операторов и ||, используя вместо них дополнительную логику. Для сокращенной оценки код следовало бы изменить так:
Принцип прекращения проверки сразу по получении ответа уместен и других случаях. Например, исследование массива на наличие четных чисел. Можно решить эту задачу, несколькими способами. Первый способ: пройтись по всему массиву и при нахождении четного числа устанавливать флаг evenNumber. Цикл поиска может выглядеть так:
Этот способ не оптимален. Лучше было бы прекращать проверку после обнаружения первого четного числа.
Пример оптимизированного кода представлен в таблице 4.1:
Таблица – 4.1 Оптимизированный цикл, выполняющий поиск числа
Источник: scienceforum.ru