----------
Всё это время я говорил о производительности скриптов, не останавливаясь на том, почему большинство вопросов, задаваемых мне о ней – в лучшем случае бессмысленные, а чаще откровенно вредные.
Такие вопросы я получал десятки раз за последние семь лет. Вот этот, например, – в конце 1990-х:
У нас есть код на VBScript, который объявляет множество переменных в часто используемой функции. Сами эти переменные нигде в коде не используются и уничтожаются на выходе из функции нетронутые. Платим ли мы скрытую цену за каждый вызов этой функции?
Какой интересный вопрос о производительности VBScript! В таком языке как Си, объявление n байт локальных переменных компилируется всего лишь в команду изменения указателя стека на n байт. Уменьшение или увеличение n не оказывает никакого воздействия на время выполнения этой команды. Так же работает VBScript, или нет? Удивительно, но нет! Вот мои исследования:
Плохое исследование №1
Что ты объявил, то и получаешь. VBScript не может знать, не выполнишь ли ты впоследствии что-то вроде этого:
- Код: Выделить всё
function foo()
Dim bar
Execute("bar = 123")
Чтобы такой код мог работать, движок VBScript вынужден во время выполнения хранить все имена всех переменных. Это вызывает дополнительные расходы на каждую переменную при каждом вызове.
(Кстати, JScript.NET действительно пытается обнаружить такой код и оптимизировать его, но речь сейчас не об этом.)
Всё же, какие именно дополнительные расходы вызываются лишними объявлениями? Так случилось, что у меня в тот день под рукой оказалась машина, настроенная для измерения производительности, так что я смог ответить:
На моей машине каждая лишняя переменная (объявленная, но неиспользуемая) требует лишние 50 наносекунд при каждом вызове функции. Эта задержка, по-видимому, растёт линейно вместе с числом объявленных, но неиспользованных переменных; я не проверял случаи крайне большого числа лишних переменных, сочтя их нереалистичными. Также я не проверял случаи очень длинных имён переменных; хотя VBScript ограничивает имена переменных до 256 символов, задержка может увеличиваться при использовании более длинных имён.
Моя машина – Pentium III 927 МГц, так что эта задержка – где-то примерно 50 тактов процессора. На этой машине не установлен VTUNE, так что я не могу назвать точное число тактов.
Это означает, что если в твоей часто используемой функции, скажем, пять лишних переменных, то каждые четыре миллиона вызовов этой функции тормозят программу на целую секунду, предполагая, конечно, что у вас такие же машины, как у меня. Очевидно, что на более слабых машинах задержка будет ещё заметно хуже.
Ещё, ты не указываешь, выполняется всё это на сервере или на клиенте. Это крайне важно при исследовании производительности!
Поскольку задержка вызывается выделением памяти в куче, на сервере она может вести себя по-разному в зависимости от использования кучи другими нитями сервера. Могут быть задержки, вызванные конкуренцией за память – мои измерения измерили только "непосредственную" процессорную загрузку; полное исследование для, скажем, 8-процессорного сильно загруженного сервера, постоянно выделяющего множество мелких строк, может дать и совершенно другие результаты.
А теперь я воспользуюсь возможностью и заявлю, что все описанные мной сейчас исследования почти совершенно бесполезны, потому что они скрывают гораздо б́ольшую проблему. В комнате есть слон, которого мы не замечаем. Сам факт, что пользователь спрашивает меня о производительности VBScript означает, что либо
а) этот пользователь – убеждённый фанат VBScript, которому просто интересно поговорить об особенностях языка;
или, что более вероятно,
б) у этого пользователя есть скрипт, который работает недостаточно быстро. Пользователю крайне важна производительность его скрипта.
Ого! Теперь понятно, почему наше исследование производительности оказывается бесполезным. Если пользователю так важна производительность, то почему же он выбрал динамический язык с поздним связыванием, со слабой типизацией, интерпретирующий неоптимизированный байт-код, – язык, специально созданный для быстроты разработки и жертвующий ради этого производительностью?
Плохое исследование №2
Если вам хочется, чтобы скрипт работал быстрее, то внимания требуют куда более важные вещи, чем 50-наносекундные задержки. Самое важное для эффективной оптимизации – найти самую неэффективную вещь и начать с неё. Например, единственный вызов, использующий необъявленную переменную, уже вносит в сотни раз б́ольшую задержку, чем от объявленной, но не использованной переменной. Единственный вызов объектной модели контейнера – в тысячи раз б́ольшую. Оптимизировать скрипт, сокращая 50-нс задержки, – это всё равно, что стричь газон, срезая самую короткую траву маникюрными ножничками и не обращая внимания на остальное. Это займёт много времени, а никаких изменений заметно так и не будет. Это иллюстрирует разницу между "активностью" и "продуктивностью". Не делайте так!
Но ещё лучшим советом было бы выбросить весь скрипт и переписать на Си всё с самого начала, раз производительность так важна.
А теперь я воспользуюсь возможностью перебить себя и скажу: да, производительность скриптов важна. Мы потратили на оптимизацию скриптовых движков уйму времени, чтобы они работали чертовски быстро для движков динамических языков с поздним связыванием, со слабой типизацией, интерпретирующих неоптимизированный байт-код. Однажды вы сталкиваетесь с фактом, что для каждой работы нужно выбирать подходящий инструмент: VBScript настолько быстр, насколько мы могли его сделать быстрым, не превращая в совершенно иной язык.
К сожалению, это второе исследование не лучше первого, потому что в комнате опять есть слон. Есть жизненно важный параметр, который не был задан, и который при исследовании производительности важнее всего остального:
Когда плохо – это достаточно хорошо?
Я пожалел себя – на самом деле я считаю эти исследования производительности "сидя в кресле" не просто бесполезными, а вредными.
Я читал статьи о скриптовых языках, которые утверждали вещи вроде "для проверки чётности числа нужно использовать And 1, а не Mod 2, потому что процессор быстрее выполняет команду And", как если бы VBScript компилировался в сильно оптимизированный машинный код. Люди, выбирающие используемые операторы на основе столь беспочвенных доводов, не напишут ясный читаемый код. Их программы будут сломанными, а "сломанная программа" – это наихудшая возможная оценка производительности, вне зависимости от скорости работы неверной программы.
Если вы хотите написать быстрый код – не важно, скриптовый или нет, – то не обращайте внимания на все эти статьи с "ценными советами", указывающие, какие операторы быстрее работают и сколько времени занимает объявление переменной. Чтобы написать быстрый код, не нужно набора дешёвых трюков – нужно исследование пользовательских запросов, задающее цели, и затем ведение тщательно спланированных измерений и коррекций, пока заданные цели не будут достигнуты.
1) План должен основываться на пользовательских запросах. Необходимо знать, каких именно целей добивается улучшение производительности.
2) Необходимо знать, что именно измерять для проверки достижения этих целей. Что именно критично – пропускная способность? время до начала вывода? до конца вывода? масштабируемость?
3) Нужно измерять систему целиком, а не только отдельные её части.
4) Измерения должны быть тщательными и постоянными.
Именно так делает команда MSN, и они умеют делать масштабируемые веб-сайты.
Я знаю, что люди хотят слышать не это. Люди относят к анализу производительности те вещи, которые, кажется, потеряли актуальность вместе с PDP-11. Скрипты, выполняемые на веб-серверах, невозможно оптимизировать, оттачивая отдельные строки кода – это не Си, где можно вычислить точные временные затраты на каждый оператор. В скрипте нужно смотреть на всю программу целиком и атаковать самые затратные вещи – иначе окажется, что огромный объём работы сделан ради нулевого выигрыша.
Но сначала нужно узнать, какие именно цели преследуются. Выясните, что важно вашим пользователям. Пользовательские приложения должны быть отзывчивыми – обработка данных может занять пять минут или час, но нажатие кнопки должно быть обработано за 0,2 секунды – иначе приложение будет казаться зависшим. Масштабируемые веб-приложения должны быть ослепительно быстрыми – разница между 25 мс и 50 мс даёт 20 страниц в секунду. Но какая у пользователя пропускная способность? Если 10Кб-страница сгенерируется на 25 мс быстрее, парень с модемом на 14000 б/с не заметит никакой разницы.
Только когда вам понятно, какие именно цели поставлены, начинайте измерения. Вы были бы поражены, если б знали, сколько человек, приходивших ко мне с просьбой помочь ускорить что-либо, не могли сказать мне, как они узн́ают, что уже достаточно. Если вы не знаете, что значит "достаточно быстро", то вы можете оптимизировать свою программу сколь угодно долго.
И если действительно окажется, что нужно пользоваться скриптовым решением, и что скрипт – именно то, что нужно ускорять, то ищите большие вещи. Помните: скрипт – это клей. Подавляющее большинство времени, потраченного на выполнение типичного скрипта – это либо подготовка вызова внешнего объекта, либо сам этот вызов. Если хотите, чтобы было единственное правило оптимизации скриптов, то вот оно: обработка данных – это очень плохо, а кода – ещё хуже. Не думайте об объявлениях, думайте о вызовах. Удаление одного вызова COM-объекта стоит десятков тысяч микро-оптимизаций.
И ещё не забывайте, что ПРАВИЛЬНО лучше, чем БЫСТРО. Пишите код как можно проще: осмысленный код можно исследовать и поддерживать, и именно поэтому он производительнее. Рассмотрим наш пример с неиспользуемым объявлением: тот факт, что оно вносит 50 нс задержки, не имеет значения. Это неиспользуемая переменная; это бессмысленный код; это ненужная помеха для программистов, поддерживающих скрипт. Именно это – его настоящее влияние на производительность скрипта: оно мешает программистам, анализирующим производительность кода, оптимизировать скрипт!