03 апреля 2012

Пример преждевременной оптимизации

Наверное, каждый, кто называет себя программистом, слышал когда-нибудь мысль Дональда Кнута о преждевременной оптимизации. ...Premature optimization is the root of all evil... и все такое. Но почему-то большинство думает, что это к ним не относится. Каждый из нас сам знает, когда оптимизировать, а когда нет.

Давайте рассмотрим простой пример. Допустим, у нас есть массив чисел a, например, значения температуры воздуха за некоторый период времени. Нам необходимо найти минимальную, максимальную и среднюю температуру за этот период. Допустим также, что в стандартной библиотеке языка программирования, с которым мы работаем, есть функции min(), max() и sum(). В общем, ничего сложного:
minValue = min(a);
maxValue = max(a);
avgValue = sum(a) / a.length;

Но мы-то с вами умные, понимаем, что поиск минимального ― один проход по массиву, максимального и суммы ― еще по одному. А на самом деле все можно написать в одном цикле:
minValue = a[0];
maxValue = a[0];
sumValue = 0;

for (int i = 0; i < a.length; i++) {
if (a[i] > maxValue) {
maxValue = a[i];
}
if (a[i] < minValue) {
minValue = a[i];
}
sumValue +=a[i]
}

avgValue = sumValue / a.length;

Вот, гордо думаем мы, теперь все будет работать значительно быстрее. Но, на самом деле, что мы выиграли? Во-первых, теоретически, вычислительная сложность второго варианта будет O(n), а первого... O(n). Во-вторых, практически все будет еще хуже. С одной стороны, т.к. то, что мы делаем является лишь частью проекта, в состав которого входит также измерение температуры, наш код будет выполняться, к примеру, лишь тогда, когда массив значений окажется заполненным. Одно измерение температуры длится несколько секунд, а все расчеты ― милисекунды. Т.е. на фоне измерений, время расчета будет совсем незаметным. С другой стороны, функции стандартной библиотеки могут иметь эффективную реализацию для данной платформы, например, с использованием векторных инструкций (SSE). Тогда первый вариант кода окажется не в 3 раза медленнее второго, а наоборот быстрее.
Мало этого, написанный нами второй вариант имеет потенциальные ошибки: что будет, если размер массива окажется больше чем максимальное значение int? а если произойдет переполнение суммы? А все это очень вероятно, если, к примеру, нам придется перекомпилировать программу для микроконтролера с меньшей разрядностью процессорного слова.
Наконец, простота реализации: мы увеличили размер программы более чем в 3 раза (не считая фигурных скобок), что явно не добавило читабельности.
Итак, мы потратили время, а значит и деньги заказчика, на то, чтобы из простого и быстрого варианта программы сделать медленный, труднопонимаемый, да еще и ошибочный. Стоит ли этого преждевременная оптимизация?

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