Пять правил оптимизации программ

Пять правил оптимизации программ

Сразу определимся с тем, что же такое «оптимизация» программы. Под оптимизацией будем понимать изменение программного кода с целью сокращения использования тех или иных ресурсов системы за счет его усложнения.

РЕКОМЕНДУЕМ: Десять советов по созданию гибкого программного кода

Правило 1. Чем позже — тем лучше

Это правило кажется самым простым в своем применении. Ведь оно поощряет сокращение объема работы. Но такое ощущение обманчиво. Очень часто возникает соблазн выполнить оптимизацию преждевременно. В результате структура программы усложняется. Читать ее становится труднее. Как следствие, код оказывается тяжело сопровождать. Но даже это не самое плохое. Проблема в том, что вы можете потратить кучу времени на оптимизацию не того, что надо.

Вывод: На первом этапе разработки пишите самый простой код (простой, но не примитивный! простой = понятный), который решает поставленную задачу, не задумываясь об ограничении ресурсов.

Правило 2. Сначала измерения — затем изменения

Предположим, первый этап пройден. Простая версия программы работает правильно, но недостаточно эффективно. И здесь возникает вопрос, а как измерять эффективность? Не бывает абстрактной эффективности. Она всегда определяется относительно чего-то. Например, эффективность относительно расхода оперативной памяти или эффективность относительно использования ресурсов процессора. В связи с этим мы приходим к следующему шагу: выберите критерии оптимизации. В качестве таких критериев можно рассматривать любую значимую характеристику приложения (от размера исполняемого файла до скорости передачи данных по сети).

Ни в коем случае не пытайтесь определить соответствие критериям «на глаз». Все равно ошибетесь. Найдите способ проведения точных (или максимально приближенных) измерений. Для некоторых характеристик это сделать проще (например, размер программы), а для некоторых сложнее (например, максимальное число одновременно обслуживаемых пользователей).

Итак, вы убедились, что программа не соответствует одному или нескольким выбранным критериям. Что теперь? Бежать переписывать все подряд? Не спешите. Если начать менять код сейчас, то вы опять будете работать вслепую, делая множество лишней работы. Нам известно, что в программе где-то есть проблема. Но где она? И вновь нам нужны дополнительные инструменты для измерений. Типичным решением является использование профилировщика. Сам по себе профилировщик — средство, которое умеет подсчитывать количество вызовов, время нахождения и другие полезные цифры для каждой процедуры программы. Реализация профилировщика может быть какой угодно (многое зависит от платформы): от внешнего приложения до библиотеки. Если нашли более подходящий инструмент для своих целей — используйте его.

Прекрасно. Допустим, нам удалось найти то узкое место, которое все тормозит. Вот с ним и нужно работать.

Вывод: Любая оптимизация должна быть осмысленной. Вполне возможно, что она и не требуется. Если не сделать соответствующие измерения, то вы об этом не узнаете.

Правило 3. Проверь алгоритмы и структуры данных

При просмотре проблемной функции в первую очередь проверьте, используются ли для решения задачи подходящие алгоритмы и структуры данных. Именно это может стать причиной неэффективной работы приложения.

Грубый пример: в функции используется линейный поиск по массиву. Если для массивов небольшого размера это вполне приемлемо, то для больших массивов это становится проблемой. В этом случае можно использовать множество, а не массив, если не требуется упорядоченность элементов. Или же вместо простого линейного поиска можно попробовать бинарный.

Еще один пример: вы используете быструю сортировку, но она работает не достаточно шустро. Проверьте улучшится ли что-то, если поменять ее на сортировку слиянием или вставками. Быстрая сортировка — не всегда оказывается наилучшей.

Замечу, что рассмотренные выше примеры лучше считать не оптимизацией, а исправлением ошибок проектирования.

Если в рассматриваемой функции уже используются наиболее подходящие стандартные алгоритмы и структуры данных, то вам не обойтись без многопоточности. Рекомендую посмотреть хороший пример из другой моей статьи, где мы ускорили алгоритм обработки изображения в 4 раза с помощью QtConcurrent.

Но и это не полный перечень возможных оптимизаций. Часто удается сократить объем необходимых вычислений, введя дополнительные неочевидные ограничения. Вспомните тот же алгоритм «Решето Эратосфена» для поиска простых чисел. Другая возможная оптимизация заключается в использовании приблизительных расчетов. Если вам достаточно двух знаков после запятой, то нет смысла искать значение до шестого. Еще одна тактика заключается в использовании таблиц констант. Например, для получения значений тригонометрических функций. Думаю, что вы и сами сможете придумать не одну уловку, которая сможет улучшить эффективность вашей программы. Все зависит от контекста.

Вывод: Начните с наиболее очевидных способов оптимизации. Обязательно проведите повторные замеры рабочих характеристик нового кода (после любых изменений), иначе как вы узнаете, что не стало еще хуже, чем было? Если этого оказалось недостаточно, то ориентируйтесь по ситуации. Попробуйте добавить параллельную обработку или приблизительные методы расчетов.

Правило 4. Задокументируй это

Если оптимизация прошла успешно, то вполне вероятно, что код, над которым вы работали, стал менее понятным. Это может оказаться проблемой в плане дальнейшего сопровождения системы. Поэтому сразу после проведения оптимизации не помешает написать комментарий, в котором будет указано:

  1. Обоснование такого кода. То есть почему понадобилась оптимизация. Желательно добавить числовые показатели, которые вы получили в результате измерений (до оптимизации и после нее);
  2. Разъяснение выбранного решения. В чем суть оптимизации. За счет чего достигается повышение эффективности и т.д.;
  3. Указание вариантов, которые вы успели попробовать, но они не оправдали ожиданий. Возможно, кто-то другой будет работать над тем же кодом и решит, что его можно сделать еще оптимальнее. Вы упростите работу коллег, если поделитесь своим опытом с ними в комментариях, указав, что уже было сделано, чтобы они не тратили время зря.

Вывод: Если код сложный, то для него требуются пояснения. В будущем эти пояснения могут помочь именно Вам.

Правило 5. Если ничего не помогло

Но что делать, если вы уже попробовали все, что можно, но программа все равно не укладывается в критерии оптимальности? Здесь у вас может быть несколько вариантов:

  1. Обсуждение текущей версии системы с заказчиком. Возможно, незначительное несоответствие в плане эффективности его устроит;
  2. Повышение системных требований. Если проблему не удалось решить программно, то можно попробовать найти аппаратное решение. Ставим больше памяти, больше процессоров, подключаем интернет-канал с большей пропускной способностью;
  3. Переход на другой (более низкоуровневый) язык программирования. Это не означает, что вам нужно переписывать ВСЕ приложение. Для этого мы и искали узкие места, требующие оптимизации. Вам достаточно переписать проблемные функции на более эффективном языке программирования в виде подключаемых модулей. Например, система написана на Java, а вы пишите модуль на C++. Или система написана на C++, а вы делаете ассемблерную вставку.

Вывод: Если у задачи нет решения на одном уровне абстракции, то оно может найтись на другом.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий