Saturday 21 March 2009

JavaScript / JScript Benchmark

Начало

Не буду лгать - я не люблю оценивать производительность javascript кода. Эта процедура содержит для основного проекта массу "ненужного" и "бессмысленного" кода:
var n = 1000; 
var start = (new Date()).getTime();/
for (var i = 0 ; i < n; i++) {
    // bla-bla-bla ...
}
var stop = (new Date()).getTime();
var duration = stop - start;
var average = duration / n; 
document.writeln(average);
Думаю, всем знакомы строчки, подобные этим. Меня может интересовать производительность функции или метода, а возможно и некоторого участка программы. Но я не хочу засорять свой еще сырой код всяким мусором, подобным этому. Я также не хочу это делать для функций, когда пытаюсь оценить - какой же алгоритм лучше.
Когда мне это окончательно надоело - я решил написать свой Benchmark. Задача заключалась в том, чтобы можно было оценить производительность не только функции, но и любого участка кода, начиная со строки n и заканчивая строкой m.

Мотивация

Начиная работу, я понимал - вполне вероятно, что такой код уже кем-то и весьма успешно реализован. Тем не менее, я начал с нуля. После завершения я решил сравнить свое решение с работами своих предшественников (ссылки в конце). Не буду скромничать - не смотря на некоторое сходство, мое решение лучше. Аргументация следующая - при существенном сходстве реализации мой код в значительной мере более универсален.
Далее я покажу - что есть, и как из него можно получить весьма удобный и простой инструмент. Частично будут рассмотрены сходства и различия настоящего решения и его предшественников.

Особенности ОО-подхода в JavaScript

Любой фрагмент кода (особенно это справедливо для JavaScript с его особым ОО-подходом) может быть телом безымянной функции. Например, код
var arr = [100, 200, 300, 400]; 
    var result = 0; 
    for (var i = 0; i < arr.length; i++) {
        result += arr[i];
    }
может быть преобразован в следующий
var arr = [100, 200, 300, 400]; 
((function() 
{
    var result = 0; 
    for (var i = 0; i < arr.length; i++) {
        result += arr[i];
    }
})(); 

Реализация

Это значит, что для любой функции можно применить некий метод, который может выполнить дополнительные действия, а именно:
Function.prototype.eval = function()
Данный метод определен для встроенного объекта Function, вызывает его определенное количество раз, при этом запоминает длительность выполнения функции и печатает некоторую статистическую информацию о ходе выполнения.
Посмотрим на примере как эго можно использовать:
var arr = [1, 2, 3, 4];

function sum(arr)
{
   var result = 0;
   for (var i = 0; i < arr.length; i++) {
      result += arr[i];
   }
   return result;
}

// исходный код
var s = sum(arr);

// модифицированный код
var s = sum.eval(arr);
То есть, сравнивая исходный и модифицированный код, можно увидеть, что отличия минимальны. Код по прежнему чист, а результат позволяет проанализировать полученную информацию.

Особенности

Данное решение не использует таймеры отчета времени и может выполняться в разных средах, как в окне браузера, так и в самостоятельном приложении. Также содержит некоторый дополнительный код, который решает проблему кроссплатформенности.
Что же было применено дополнительно? В отличия от аналогов, данный пример работает для большинства случаев - JavaScript/JScript. Для этого дополнительно определено несколько полезных свойств:
- Function.prototype.evalCount - целочисленный параметр хранит количество итераций, или количество раз исполнения кода, значение по умолчанию 1000.
- Function.prototype.evalDuration - целочисленный параметр хранит продолжительность выполнения кода.
- Function.prototype.evalPrint = function() - вспомогательная функция для вывода статистики о производительности, учитывает различия сред исполнения и вызывает соответствующий метод для вывода количества итераций и продолжительности исполнения кода.
По умолчанию, производится оценка и выводится время исполнения кода для 1000 итераций. Параметр Function.prototype.evalDuration вычисляется в процессе и не имеет значения по умолчанию.
Естественно, эти параметры можно переопределить, например, изменить количество итераций, или переопределить свой метод отображения статистики. При чем, это можно сделать как глобально через прототип, так и конкретно для определенной функции. Например:
sum.evalCount = 10240; // 10K iterations
var result = sum.eval(1, 2, 3, 4);

Преимущества

Предложенная методика в отличие от аналогов
1. позволяет легко внедрить в свой код возможности оценки производительности с минимальными изменениями основного кода;
2. имеет расширенные возможности контроля;
3. она независима от текущей платформы и позволяет использовать данное расширение без изменений как в среде JavaScript, так и в JScript (Windows Scripting Host).

Полное решение

Решение платформно-независимо. Тестировалось для IE6, IE7, FF2.0.0.15, FF3.0.7 и Windows Scripting Host. Распространяется по лицензии GPL. Листинг исходного кода доступен по этой ссылке. Там же - ссылка на скачивание.

Ссылки по теме

1. John Resig's blog
2. webtoolkit site

4 comments:

  1. " Скачать исходный код можно по этой ссылке. "
    - а вот с этим проблема, т.к по ссылке GOOGLE предоставляет страничку с ЛИСТИНГОМ программы - скачать не получается, а копипаста не удобна да и не корректна!

    подскажите, как получить код ОДНИМ файлом, без лишних телодвижений? (или я невнимательный и DOWNLOAD доступен?)

    ReplyDelete
  2. Вы правы, фраза не совсем корректна и я ее подкорректировал. Ссылка дана исключительно для отслеживания переходов по ссылкам. Однако DOWNLOAD доступен. Чтобы скачать "чистый" код без разметки - найдите во вставке справа текст со ссылкой "View raw file".

    ReplyDelete
  3. спасибо, теперь получилось

    ReplyDelete