Premature optimization is the root of all evil

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

На самом же деле, не любая попытка сделать код быстрее является преждевременной. И я последнее время развлекал себя мыслями по поводу того, как же отличить одно от другого. Ничего теоретически обоснованного не надумал (и вообще уверен, что никакой четкой теории тут быть не может). Но некоторыми наблюдениями хочу поделиться.

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

Однако далеко не все архитектурные решения являются уникальными для программы, которую пишет программист. Даже я бы сказал, очень многие архитектурные решения давно уже очень хорошо изучены, и про них заранее известно, чем они чреваты.

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

Еще один пример — вывод на веб-страницу списка строк из таблицы БД вместе с какой-нибудь агрегированной информацией. Например список топиков форума с цифрой количества статей в каждом из них. Большинство веб-программистов уже заранее знают, что если сделать простую выборку топиков, а потом для каждого из них делать выборку количества, то это будет медленнее, чем сделать одну выборку, воспользовавшись SQL-запросом с GROUP BY. Во-первых, алгоритмическая сложность первого подхода — O(n) против O(1) у второго. А во-вторых, это подтверждается практикой: уже на количестве строк около 20 ваш форум начинает ощутимо тормозить. Поэтому такую оптимизацию я совсем не хочу относить к преждевременной, потому что этот bottleneck заранее известен. Причем возможно скоро такие оптимизации будут скрыты внутри фреймворков, либо внутри серверов БД, и тогда этот выбор станет таким же тривиальным, как и выбор алгоритма сортировки: просто бери готовый.

Из всего этого я сформулировал пока свое главное наблюдение.

Антитезой преждевременной оптимизации является просто напросто правильная архитектура.

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

Комментарии: 18

  1. Юревич Юрий

    Новое - хорошо забытое старое. Тема преждевременной оптимизации раскрыта в Алёнином комментарии.

  2. Евгений

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

    А начальник начинает тебя дрючить, что у тебя неоптимально выполняется конкатенация строк, например, типа:

    username = "%s@%s" % (form["name"], form["domain"])
    

    вместо

    username = "@".join(form["name"], form["domain"])
    

    (пример от балды, неохота вспоминать точные детали, к тому же с таким optimization freak'ом я писал на Perl).

    Цепляние к подобным мелочам меня начинало бесить, потому что экономия одной миллисекунды в таком коде абсолютно не нужна и никогда не будет замечена. Она даже в цикле никогда не будет выполняться. К чему эта экономия байтов и тактов процессора???

    И таких optimization freak'ов мне встречалось намного больше, чем тех, кто считает преждевременную оптимизацию злом.

  3. Давид Мзареулян

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

    Если серьёзно, то этим просто нужно переболеть. И, если уж откровенно, «правильная архитектура» — куда более сферический конь, чем преждевременная оптимизация…

  4. bialix

    согласен. "правильная архитектура" нужна в начале работы, но плоды от ее наличия или отсутствия ощущаешь в конце.

  5. Алексей Захлестин

    Я бы сказал так: правильная архитектура – это когда ты понимаешь, что потом сможешь вместо этого неэффективного куска безболезнено вставить эффективный (если будет нужно)

  6. iv

    существует еще другая крайность "premature pessimisation" что в не лучше.Обычная лень + небрежность.

  7. undebugger

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

    Вот-вот, оно самое: использование ярлыка направо и налево привело к тому, что человек вынужден оправдываться, если применяет более эффективное решение.

  8. Ivan Sagalaev

    Ну если "оправдываться" заменить на "обосновывать", то это наверное хорошо :-)

  9. jetxee

    Я бы сказал так: правильная архитектура – это когда ты понимаешь, что потом сможешь вместо этого неэффективного куска безболезнено вставить эффективный (если будет нужно)

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

  10. Проходил мимо

    Пример с group by "неправильно" передает смысл преждевременной оптимизации имхо, потому как должно быть очевидно использование group by в этом месте, а если человек пишет в 2 этапа, значит он просто не знает про group by, а если знает, но считает, что это будет по каким-то соображениям медленнее, чем его колдунство - это уже пример преждевременной пессимизации

  11. oqbo

    Сказать однозначно "плохо" это или "хорошо" нельзя.
    Смотря с какой стороны на этот вопрос смотреть, такой может быть и ответ.

  12. Sergey Kovalevsky

    Я бы сказал так: правильная архитектура – это когда ты понимаешь, что потом сможешь вместо этого неэффективного куска безболезнено вставить эффективный (если будет нужно)

    Это не правильная архитектура. Это вобще не архитектура, а писаные на вилами на воде заметки подходящие для проекта которым занимается один человек, да и то, до определенного момента.

  13. Давид Мзареулян

    Например список топиков форума с цифрой количества статей в каждом из них. Большинство веб-программистов уже заранее знают, что если сделать простую выборку топиков, а потом для каждого из них делать выборку количества, то это будет медленнее, чем сделать одну выборку, воспользовавшись SQL-запросом с GROUP BY.

    Кстати, это не так. В смысле, это плохой пример. На моём сайте почти такая же ситуация — надо выбрать из списка объекты (limit-offset), их параметры (разные) и число комментов к ним, при том что база довольно большая. Так вот, честная выборка всего этого за один проход требует помимо group by четырёх-пяти join-ов с другими таблицами. И это получается медленно. А быстро получается, если выбрать сначала id-ы нужных (по limit-offset объектов), сэкономив на join-ах и на группировках, а потом, вторым запросом, вытянуть всё остальное только для этоих немногих id-ов. Разумеется, это не «для каждого делать выборку», но с точки зрения лобовой оптимизации всё равно не самый очевидный путь. И тем не менее. Хотя, в теории, база должна сама там у себя внутри всё это делать, все индексы на месте.

    Можно и другой пример привести — пусть у нас есть ORM с совершенно элементарным write-through кэшем, скажем, в мемкеше. Тогда нам гораздо выгоднее сначала выбрать N id-ов объектов, а потом сделать N отдельных выборок данных этих объектов. Потому что все эти выборки будут просто доставать данные из кэша, и это будет быстро именно потому что кэш заточен под простые запросы, а их количество особой роли не играет. А сделать кэш, который правильно распознает запросы с группировкой и джойнами, и правильно сбрасывается при апдейтах — куда как сложнее и очень вряд ли масштабируемее.

  14. Ivan Sagalaev

    Разумеется, это не «для каждого делать выборку»

    Именно :-). Это все те же O(1) вместо O(n) :-)

  15. Давид Мзареулян

    Именно :-). Это все те же O(1) вместо O(n) :-)

    Э-э… мы же не знаем, сколько именно “O” остаётся за скобками внутри sql-сервера? А его возможности по оптимизации небезграничны.

    Ну и второй пример с виду — как раз O(n). А в реальности он гораздо масштабируемее, чем выборка всего сразу одним запросом.

  16. ph

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

  17. Ivan Sagalaev

    Не знаю, что мешает... Статья же не про статьи в топиках, это был просто пример.

  18. Сергей

    Преждевременная оптимизация вредна по определению, поскольку она преждевременна. :) А вот упреждающая полезна, когда очевидна.

    Вот и вся теория.

    Не пренебрегай очевидной оптимизацией. Разве не очевидно?

Добавить комментарий