Даже среди программистов не так уж много знаменитых ученых в области теории вычислительных машин и систем, но Дональд Кнут по праву является одним из них. Он написал книгу «Искусство программирования» (англ. The Art of Computer Programming) — одну из основополагающих работ в компьютерных науках, многотомное руководство, над которым Кнут с самоотверженностью средневекового монаха-отшельника продолжает работать с 1962 года{18}
. Он тщательно проводит исследования, пишет с чрезвычайной осторожностью и снабжает свои выпуски такими подзаголовками, как «Введение в комбинаторные алгоритмы и Булевы функции, побитовые решения и методы», «Двоичные диаграммы решений» и «Формирование всех деревьев — история комбинаторной генерации». Для разработчиков программного обеспечения, которые воспринимают свою работу всерьез, Кнут является непревзойденным мастером своего дела. Вот что он говорит об оптимизации:Программисты тратят огромное количество времени на размышления или беспокойство о скорости работы некритичных частей своих программ, и эти попытки добиться эффективности на самом деле оказывают большое отрицательное воздействие, если говорить о поиске ошибок и поддержании рабочего состояния. Мы должны забывать о низкой эффективности, скажем, в 97 % случаев:
Оптимизация — это процесс, когда программисты пытаются заставить код работать быстрее. Но разве не для этого была создана программа PLT? И вроде бы оптимизация — это хорошо, правда? Не всегда. И если принимать на веру численную оценку Кнута (а она заслуживает доверия, поскольку этот человек говорит только о вещах, имеющих под собой основание), оптимизация плоха в 97 % случаев. Почему?
Программы — это, если уж на то пошло, просто длинные списки инструкций для компьютера, и, хотя компьютеры работают очень быстро, их возможности не безграничны. Чтобы сделать быстрое программное обеспечение, инструкции в программе должны быть настолько эффективны, насколько это возможно, но не всегда очевидно, какую из них можно будет выполнить быстрее.
Приведем пример. Если я приглашу вас к себе на кухню и попрошу:
• Вытащить баночку горчицы из холодильника.
Вы легко справитесь с этим заданием, поскольку на моей кухне специй и приправ довольно много. Также понятно, что на выполнение этой инструкции уйдет меньше времени, чем потребуется для того, чтобы сделать указанное в инструкции той же длины:
• Пойти в супермаркет и купить баночку горчицы.
Поскольку в одних инструкциях заключается более высокий уровень понятийной сложности, чем в других, на их выполнение может потребоваться больше — иногда даже намного больше — времени. Если я останусь у себя на кухне во время теста с баночкой горчицы, я успею вытащить ее из холодильника несколько десятков раз, пока вы будете ходить в магазин. Но, опять же, многое зависит от того, далеко ли находится супермаркет. Для понимания сущности оптимизации ключевое слово в предыдущем предложении — это «зависит». Тщательно разработанное программное обеспечение основывается на искусной взаимосвязанной сети зависимостей между отдельными компонентами, и работа с этими взаимоотношениями — неотъемлемая часть создания сложного ПО, такого, как браузер. Пример с банкой горчицы иллюстрирует, насколько принципиальна эта проблема, и показывает, как широко она распространена и как трудно программисту с первого взгляда понять, с какой скоростью может работать определенный кусок кода, даже если суть инструкции полностью ясна.
Как это связано с оптимизацией? Вот ряд инструкций для следующего кухонного задания:
• Вытащить все из моего холодильника.
• Расставить предметы на стойке.
А вот попытка оптимизации:
• Вытащить все из моего холодильника.
• Расставить предметы на стойке.
• Сделать как можно меньше подходов.
Эта третья инструкция вносит поправку о скорости выполнения задания. Количество ходок между холодильником и стойкой ложится в основу проблемы, и подразумевается, что если его получится уменьшить, то вся операция в целом может быть выполнена быстрее.