Преждевременная оптимизация - корень всех бед

Oct 24, 2011 18:30


Дональд Кнут запустил в программистский мир крылатое выражение: преждевременная оптимизация - корень всех бед. Однако, дьявол кроется в деталях. Попробуем разобраться в каком смысле здесь нужно понимать слова преждевременная и оптимизация, чтобы понять откуда берутся все беды.
Оптимизация программной системы это увеличение её производительности (скорости реакции, пропускной способности и т.п.) и/или уменьшение потребления ресурсов (процессорного времени, памяти и т.п.). Та оптимизация, которая приводит к бедам, всегда подразумевает какой-нибудь компромисс. Компромисс может быть между разными аспектами производительности или потребления ресурсов, а также между ними и такими аспектами качества кода, как простота поддержки. Например, увеличивая скорость реакции системы мы потребляем больше процессорного времени, увеличивая пропускную способность мы уменьшаем скорость реакции, уменьшая потребление памяти мы усложняем код и т.п.
Одну и ту же задачу стоящую перед программистом можно всегда решить разными способами. Например, в языке Java можно проверить наличие в строке символа с помощью регулярного выражения и метода String.matches:
boolean hasAt = s.matches(".*@.*"); // (1)
а можно для этого воспользоваться методом String.contains:
boolean hasAt = s.contains("@"); // (2)
Второй способ всегда быстрей, требует меньше ресурсов, намерение программиста по коду более понятно (название метода отражает суть выполняемой операции) и шансы допустить ошибку при будущем изменении (например, при изменении символа, который нужно искать в строке) минимальны.
Ошибочно считать злой оптимизацией такое изменение кода, которое делает его лучше во всех возможных отношениях. Программист, который изменяет код ищущий символ в строке с помощью String.matches на метод String.contains, не оптимизирует его, а просто исправляет недочет в исходном коде, улучшает его. Умение писать совершенный код это то, что отличает опытного программиста от новичка. Потенциально огромное число способов решения той или иной задачи делает создание совершенного кода очень сложной задачей. Это одна из причин почему библиотеки кода с большим числом возможностей требуют много времени, чтобы их освоить в совершенстве. Ведь это требует знания всех её возможностей и умения найти тот самый код, который оптимально решает поставленную задачу.
А что если мы будем проверять наличие символа с помощью метода String.indexOf?
boolean hasAt = s.indexOf('@') >= 0; // (3)
Этот метод еще быстрей, чем второй, но намерение программиста становится менее очевидно. Это уже компромисс между производительностью и простотой кода. Простота кода тесно переплетена с аспектами стиля. Если в проекте производительность и скорость работы стоят как ключевые задачи, то использование 3-го способа при написании кода может быть приемлемо, в то время как для проекта где такие задачи не стоят, может быть и нет. При написании кода очень важен контекст, в котором решается задача. С ростом объема кода в проекте, похожие задачи возникают в разных местах. В совершенном коде, похожие задачи решаются похожим образом, приобретая форму шаблонов, которые опытные программисты узнают с одного взгляда и/или путем создания дополнительных вспомогательных библиотек.
Продолжая пример выше, замена поиска символа с помощью String.contains на вручную написанный цикл проверки символа за символом это уже чистой воды злая оптимизация. Здесь в жертву приносится простота и понятность кода, с целью получения потенциального выигрыша в производительности. Будет ли этот более сложный код написан без ошибок? Материализуется ли какой-либо заметный прирост производительности (в этом примере - нет)? Это ключевые вопросы, на которые приходится отвечать при оптимизации кода. Отрицательный ответ на любой из них это риск, который несет с собой любая оптимизация.
Что же такое преждевременная оптимизация? Это оптимизация, сделанная без детального анализа задачи, в первую очередь, без детального анализа узких мест программной системы. Необходимость глубокого анализа это прямое следствие того, что любая оптимизация это компромисс.
Обычно, говоря о преждевременной оптимизации, имеют в виду оптимизацию, которая была сделана в процессе написание кода, а не после написания, запуска и анализа производительности в соответствующих сценариях работы системы.
В Devexperts мы практически постоянно заняты написанием высокопроизводительных систем и/или систем обрабатывающих большие объемы данных из-за специфики специализации на торговле финансовыми инструментами. При написании таких систем, над производительностью системы приходится думать уже на этапе архитектуры и дизайна системы, еще до того, как написана хоть одна строчка кода. Ведь архитектуру и дизайн системы очень сложно изменить, если в процессе эксплуатации выявятся недостатки, которые нельзя устранить оптимизацией кода в рамках заложенной в систему архитектуры. Как тут избежать рисков преждевременной оптимизации?
Полностью аналогично оптимизации кода -- оптимизация архитектуры будет преждевременной, если она сделана без наличия данных. Для создания совершенной архитектуры нужно знать планируемые объемы обрабатываемых данных, ожидаемую нагрузку и т.п. Это позволит сравнить различные потенциальные способы организации системы с точки зрения ожидаемого количества взаимодействий между модулями, потребления ресурсов сети, памяти, процессора и т.п. и выбрать оптимальную архитектура для решаемой задачи. Часто это требует написание прототипов кода, чтобы проверить те или иные предположения, сделанные на этапе архитектуры и дизайна системы. Например, при создании системы высокопроизводительной системы передачи сообщений QDS, которая сейчас является ядром продукта dxFeed, необходимо было на этапе дизайна решить каким образом представлять данные о сообщениях, ибо это решение повлияет на все внутренние и внешние API. Ведь высокопроизводительная система не может себе позволить преобразовывать данные из некоего внутреннего формата в формат внешнего API, если вся суть системы заключается в быстрой передаче данных между модулями и именно эта операция является узким местом всей системы в целом.
UPDATE: О других аспектах программисткой деятельности написано в заметке " О программистах и компромиссах программирования".

programming, rant, performance

Previous post Next post
Up