В статье рассмотрены основные методы оптимизации программного кода. Приведена классификация методов оптимизации. Приведены главные принципы написания эффективного кода.
Ключевые слова: программный код, эффективность, методы оптимизации, программное обеспечение.
На данный момент индустрия информационных технологий стала одной из самых дорогостоящих в мире. Тестирование ПО не может гарантировать абсолютного устранения ошибок, поэтому актуальность разработки эффективного и безошибочного программного кода очень высока. Очевидно, что подобный программный код обязан быть максимально оптимизированным.
Рабочий, но примитивный, программный код зачастую нуждается в улучшении. Главной проблемой подобных ситуаций является алгоритм, который не охватывает все аспекты, поставленной программисту задачи. Шаблонные алгоритмы с большой вероятностью не использует все тонкости работы с процессором. Большинство современных процессоров используют либо многоядерность или многопоточность, также имеют множество различных блоком для совершения разных операций, такие как блок регистров или арифметически-логических операций. Программному инженеру необходимо учитывать все возможности для оптимизации своего кода под современные процессоры.
Рис. 1. Строение современного процессора
Оптимизация — последовательность эквивалентных преобразований исходной программы, уменьшающих ее временные показатели и затраты по памяти. Эффективность оптимизации зависит от отношения эквивалентности и от размера участка экономии, на котором эта оптимизация проводится (обычно оптимизированной программе разрешается иметь большую область определения, чем исходной) [1]. Оптимизацией не добиваются существенного улучшения алгоритма программы, можно только утверждать об улучшении реализации этого алгоритма. В удачных случаях оптимизация может ускорить программу в несколько раз. Далее в статье описаны классификации оптимизации программного кода.
Существуют две основных классификации оптимизации:
— машинно-зависимая(низкоуровневая);
— машинно-независимая(высокоуровневая).
Из названия классификаций становится ясно, что в машинно-зависимых оптимизациях используются особенности архитектуры процессоров, низкоуровневые конструкции для лучшего взаимодействия кода и процессора. А машинно-независимые оптимизация затрагивает структуру кода, включая паттерны и стили программирования. Машинно-зависимая оптимизация является более эффективной, по сравнению с независимой, так как с ее помощью учитываются особенности конкретной среды, однако машинно-зависимый оптимизатор не переносится в другую среду. С другой стороны, преобразование программного кода на уровне языка увеличивает общую эффективность программы и допускает дальнейшее развитие и сопровождение кода.
Также для качества оптимизации является важным размер фрагмента программы, в рамках которого производится оптимизирующее операции. Чем больше данный участок, тем больше информации о свойствах программы доступно оптимизатору.
Также бывают классификации, в зависимости от области их применения. Peephole-оптимизация (англ. peephole — «глазок»), при таком виде рассматривают несколько соседних графов представления программы, чтобы увидеть, можно ли с ними произвести какую-либо трансформацию с точки зрения цели оптимизации.
Например, удвоение переменной может быть более эффективно выполнено при помощи левого сдвига или путем сложения переменной с такой же.
— локальные, подразумевает рассмотрение одного базового блока за один шаг. Так как в базовые блоки не обладают способностью переходов потока управления, эти оптимизации требуют незначительного анализа (экономя время и снижая требования к памяти);
— внутрипроцедурные, при такой оптимизации задействовано гораздо больше информации, чем в локальной. Данный метод позволяет достичь более внушительного прироста эффективности, но при этом часто требуются ресурсозатратные вычисления. В случае наличия в оптимизируемой программной единице глобальных переменных — подобная оптимизация будет трудновыполнима;
— межпроцедурные, в данном виде анализируют абсолютно весь код программы. Подобные оптимизации могут быть более эффективным по сравнению с другими методами. Такие оптимизации обычно используют сложные методы, например, вызов функции замещается копией тела функции.
Эффект оптимизации получается путём применения серии разнородных оптимизирующих методов. Оптимизация программного кода обычно проходит в несколько стадий. Рассмотрим каждую из них.
— фрагментация. Под фрагментацией понимается выделение некоторого участка программы, к которому может быть применено преобразование. Задачу фрагментации решает анализ потока управления;
— проверка контекстных условий, то есть выяснение применимости оптимизирующего преобразования к данному фрагменту;
— преобразование. Применение оптимизации к выбранному фрагменту.
Далее рассмотрим основные методы машинно-независимой оптимизации.
Оптимизация циклов. В начале оптимизации кода программистом необходимо рассмотреть код на наличие неэффективных циклов, так как это самое интенсивное место программы. Зачастую именно циклы выполнены с дополнительной нагрузкой. Необходимо просмотреть итерации и выявить вызовы, которые можно вынести за пределы цикла. Оптимизация циклов даёт большой прирост к скорости выполнения программного кода.
Лишние обращения к памяти. Большинство программ в ходе своего выполнения используют память для выполнения функций чтения и записи. Данные обращения занимают много времени. Лучше всего работать с регистрами процессора, а не с памятью. Для программ желательно искать возможность внедрить временную локальную переменную, в которую производить запись, и через некоторое время произвести перезапись из этой переменной в основную память.
Ассоциативность. Свойство операций, позволяющее осуществлять последовательность их выполнения при отсутствии явных указаний на очерёдность при равном приоритете [2]. Во время написания программного кода должно учитываться какая ассоциативность применяется в используемом языке программирования. Рассмотрим пример. Предположим, в последовательности чисел с плавающей запятой существуют очень маленькие числа и очень большие. Если сначала умножить очень маленькие, то на выходе программы получим ноль. Умножая все оставшиеся числа на ноль, мы в итоге получим ноль. Если же изначально очень маленькие мы будем умножать на очень большие, в итоге можем получить правильный результат.
Векторизация. Новые процессоры поддерживают специальные расширения, называемые SSE или AVX [3], которые дают возможность работать над векторами данных. В процессоре есть векторные регистры, называемые “ %ymm0- %ymm15”, размером 16 или 32 байта. Текущие AVX регистры имеют размер 32 байта и могут содержать четыре 64-битных числа, или восемь 32-битных числа, не важно целых или с плавающей точкой. Данное расширение позволяет выполнять арифметические операции над четырьмя или восьмью числами параллельно, путём использования двух 32- битных регистров.
Условная передача данных. Процессор выполняет предвыборку, то есть считывает команды наперед. В случае если ему попадается ветвление (например, команды ассемблера je, jg, jl) [4], происходит попытка выбора ветви направления вычислений. Если выбор неверный, то теряет несколько тактов. Это называется условная передача управления. Идея оптимизация заключается в том, чтобы сократить число ветвлений в программном обеспечении, сделав поток выполнения более прямым. Для этого некоторые передачи управления оптимально заменяют на передачу данных.
Подводя итоги, можно сказать, что современный процессор имеет огромную вычислительную мощь. Но для того, чтобы пользоваться ей необходимо правильно структурировать свой код и писать его в определённом стиле. Найти уязвимость в оптимизации кода довольно сложно, поэтому обычно анализ совмещают с экспериментом: пробуют разные подходы, делают измерения производительности, исследуют код для обнаружения узких мест.
Литература:
- Лекция 11: Оптимизация программного кода // ИНТУИТ. Национальный открытый университет URL: https://intuit.ru/studies/courses/26/26/lecture/815 (дата обращения: 10.07.2022).
- Очерёдность операций // Википедия. Свободная энциклопедия URL: https://ru.wikipedia.org/wiki/Очерёдность_операций (дата обращения: 09.07.2022).
- Популярно об MMX, SSE и AVX // Российское информационно-аналитическое веб-издание «i2HARD» URL: https://i2hard.ru/publications/26720/ (дата обращения: 08.07.2022).
- Оптимизация кода: процессор // Российский информационный портал «habr» URL: https://habr.com/ru/post/309796/ (дата обращения: 07.07.2022).