Примечание: ниже перевод статьи "JavaScript optimization, are chained calls expensive?". В ней автор тестирует, насколько медленнее производятся цепочки вызовов функций по сравнению с их кешированными аналогами. В конце приведены результаты моих тестов производительности.
Sree Kotay в своем блоге оставил небольшую заметку о JavaScript. (Если вас интересуют технические подробности, я рекомендую ознакомиться с презентацией Simon Willison "A (Re)-Introduction to JavaScript".) В своем блоге Sree пишет следующее:
Чтобы разобраться с теми различиями для простейшего случая, которые следуют из понимания (очевидных) основ JS оптимизации, стоит осознать, что:
for (i=0; i < 100; i++) a.b.c.d(v);...ЗНАЧИТЕЛЬНО медленнее, по крайней мере, в JavaScript, чем:
var f=a.b.c.d; for (i=0; i < 100; i++) f(v);...потому что, в конце концов, JS — это динамический язык. Я предоставлю несколько более конкретных советов для JavaScript и тестов производительности, которые прояснят эту ситуацию.
Конечно, я это интуитивно понимаю и согласен с Sree, что последний вариант должен быть быстрее, но насколько точно быстрее? Действительно ли символические ссылки в современных JavaScript-движках настолько медленные? Не пользуются ли JavaScript интерпретаторы преимуществами JIT компиляции байткода и оптимизации байткода, которая предполагается, что если у вас написан первый вариант кода, он преобразуется внутри движка во второй вариант? (Я не знаю, насколько это возможно исходя из статического анализа, я даже не уверен в этом — это просто мое предположение.)
Предполагая, что у нас нет возможности оптимизировать неэффективные вызовы, осуществленные в первой форме ... какую потерю в производительности нам ожидать? 1%? 10%? Является ли это настолько весомым различием, чтобы мы преобразовали наш код соответствующим образом, чтобы избежать вызовов в первой форме? Будучи слишком ленив, я провел следующее тестирование только в Firefox 1.5.0.1 здесь на моем 2,2 Ггц Dell C840:
<script language="JavaScript"> var v = "Привет, медвед!"; var a = { b: { c: { d: function(arg) { return arg; } } } }; document.write("<p>a.b.c.d(\"Превед, медвед!\") = " + a.b.c.d("Превед, медвед!") + "</p>"); var start = new Date(); for (var i = 0; i < 1000000; i++) { a.b.c.d(v); } var now = new Date(); document.write("<p>Время выполнения: " + (now - start) + "</p>"); start = new Date(); var f = a.b.c.d; for (var i = 0; i < 1000000; i++) { f(v); } now = new Date(); document.write("<p>Время выполнения: " + (now - start) + "</p>"); </script>
Вывод:
a.b.c.d("Превед, медвед!") = Превед, медвед! Время выполнения: 2374 Время выполнения: 1652
Так как JavaScript-объект Date
выдает время в миллисекундах, мы проводим 1 миллион итераций в 2374 миллисекунды или 2,4 микросекунды на итерацию для первого случая. А во втором 1 миллион итераций осуществляется за 1652 миллисекунды или 1,7 микросекунды на итерацию по второй форме. Мы получаем разницу в 0,7 микросекунды на итерацию или 29%. (Да, мои математические способности, на самом деле, не являются такими выдающимися, поэтому я могу и ошибаться. Пожалуйста, перепроверьте мои выкладки и напишите, пожалуйста, если я где-то ошибся.)
Итак, выигрыш в 29% действительно впечатляет, но он дает всего лишь 0,7 микросекунды на итерацию, что не стоит того, чтобы это оптимизировать. И я полагаю, что есть куча другого кода, в которой можно применить оптимизационные техники с гораздо большей эффективностью. Другими словами, эти издержки в 29% при выполнении скрипта, по-видимому, не находятся в тех критических 90% времени выполнения скрипта, на которые нужно направить свои усилия при оптимизации.
Примечание: далее следует мои результаты для разных браузеров и несколько комментариев. Первое число соответствует первому случая, второе — второму.
Internet Explorer ведет себя примерно одинаково в обоих случаях. Скорее всего, этот шаблон в нем уже оптимизирован. Firefox 2 неприятно удивил. Вполне возможно, что сохранение цепочки вызовов в переменной, на самом деле, не уменьшают эту цепочку, а только увеличивают ее (сохраняется ссылка на цепочку вызовов). А может быть, этому есть и другое объяснение. Opera 9.5/Safari 3 ведут себя, как и ожидалось. Но их выигрыш и доля использования слишком малы, чтобы на них ориентироваться.
В общем, единственный общий совет для тех, кто работает со сложными JavaScript-приложениями: ближе к финальной стадии попытаться использовать несколько техник в качестве тестирования и подробно записать результаты для распространенных браузеров: они могут оказаться вполне нетривиальными. Ну и выбрать самый оптимальный случай, естественно.
UPD: в комментариях AKS предложил ссылку на вариант, который работает быстрее и в Firefox:for (var i = 0, d = a.b.c; i < 1000000; i++) { d.d(); }
Результаты: IE6 (2875), IE7 (3453), Firefox (1687), Opera 9.5 (1266), Safari 3 (468). Opera и Safari немного проигрывают, но для них можно сделать, например, специальное ветвление.