Долгосрочные задачи производительности JavaScript - PullRequest
20 голосов
/ 28 июля 2011

Я заметил здесь вопрос на днях ( Сокращение загрузки процессора Javascript ), и я был заинтригован.

По сути, парень хотел зашифровать некоторые файлы символ за символом.Очевидно, что все это за один раз заблокирует браузер.

Его первая идея состояла в том, чтобы делать это порциями по 1 килобайту за раз, затем делать паузу в X мс, чтобы позволить пользователю продолжать взаимодействовать со страницей между обработками.Он также подумал об использовании webWorkers (лучшая идея), но, очевидно, это не кросс-браузер.

Теперь я не хочу вдаваться в подробности, почему это, вероятно, не очень хорошая идея в javascript.Но я хотел посмотреть, смогу ли я найти решение.

Я вспомнил, как смотрел видео Дугласа Крокфорда на конференции js conf .Видео было связано с node.js и циклом событий.Но я вспомнил, как он говорил о разбиении долго выполняющихся функций на отдельные фрагменты, поэтому вновь вызываемая функция переходит в конец цикла событий.Вместо того, чтобы засорять цикл событий долго выполняемой задачей, предотвращая что-либо еще.

Я знал, что это решение заслуживает моего расследования.Как передовой разработчик, я никогда не сталкивался с чрезвычайно долго выполняемыми задачами в JS, и мне было интересно узнать, как их разбить и как они выполняются.

Я решил попробовать рекурсивную функцию, котораявызывает себя изнутри setTimeout 0 мс.Я полагал, что это обеспечит разрывы в цикле событий для всего, что хочет случиться во время его работы.Но я также подумал, что, хотя больше ничего не происходит, вы получите максимум вычислений.

Вот что я придумал.

(Я собираюсь извиниться за код. Я экспериментировал в консоли, так что это было быстро и грязно.)

function test(i, ar, callback, start){
    if ( ar === undefined ){
        var ar = [],
        start = new Date;
    };
    if ( ar.length < i ){
        ar.push( i - ( i - ar.length )  );
        setTimeout(function(){
            test( i, ar, callback, start);
        },0);
    }
    else {
        callback(ar, start);
    };
}

(Вы можете вставить этот код в консоль, и он будет работать)

По сути, то, что делает функция, - это получение числа, создание массива и вызов себя, в то время как array.length < number пока подталкивает счет.в массив.Он передает массив, созданный при первом вызове, всем последующим вызовам.

Я проверил его, и он, кажется, работает точно так, как задумано.Только его производительность довольно плохая.Я проверил это с помощью ..

(опять же, это не сексуальный код)

test(5000, undefined, function(ar, start ){ 
    var finish = new Date; 
    console.log(
        ar.length,
        'timeTaken: ', finish - start 
    ); 
});

Теперь я, очевидно, хотел знать, сколько времени это заняло, приведенный выше код занял около 20 секунд.Теперь мне кажется, что для JS не нужно 20 секунд, чтобы сосчитать до 5000. Добавьте к этому тот факт, что он выполняет некоторые вычисления и обработку для помещения элементов в массив.Но все же 20-е годы немного крутые.

Поэтому я решил создать несколько одновременно, чтобы увидеть, как это повлияло на производительность браузера и скорость вычислений.

(код не становится более сексуальным)

function foo(){ 
test(5000, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 1'  ) });
test(5000, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 2'  ) });
test(5000, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 3'  ) });
test(5000, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 4'  ) });
test(5000, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 5'  ) });
};

Таким образом, всего пять, запущенных одновременно и не вызывающих зависания браузера.

после завершения процесса все результаты возвращаются практически в одно и то же время.для их завершения потребовалось около 21,5 с.Это всего лишь на 1,5 секунды медленнее, чем один сам по себе.Но я перемещал мышь по окну на элементах, которые имели :hover эффекты, просто чтобы убедиться, что браузер все еще реагирует, так что это может объяснить некоторые издержки 1,5 с.

Так как эти функцииочевидно, работают параллельно, в браузере осталось больше вычислительного сока.

Кто-нибудь может объяснить, что здесь происходит с точки зрения производительности, и дать подробные сведения о том, как улучшить такие функции?

Просто чтобы сойти с ума, я сделал это ..

function foo(){
    var count = 100000000000000000000000000000000000000;  
    test(count, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 1'  ) });
    test(count, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 2'  ) });
    test(count, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 3'  ) });
    test(count, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 4'  ) });
    test(count, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 5'  ) });
};

Он работал все время, пока я писал этот пост, и все еще продолжаю его.Браузер не жалуется или зависает.Я добавлю время завершения, как только оно закончится.

Ответы [ 4 ]

14 голосов
/ 31 июля 2011

setTimeout не имеет минимальной задержки 0ms. Минимальная задержка составляет от 5 мс до 20 мс в зависимости от браузеров.

Мое личное тестирование показывает, что setTimeout не помещает вас обратно в стек событий сразу

Живой пример

У него есть произвольная минимальная задержка перед повторным вызовом

var s = new Date(),
    count = 10000,
    cb = after(count, function() {
        console.log(new Date() - s);    
    });

doo(count, function() {
    test(10, undefined, cb);
});
  • Выполнение 10000 из них в параллельном отсчете до 10 занимает 500 мс.
  • Выполнение 100, считая до 10, занимает 60 мс.
  • Выполнение 1, считая до 10, занимает 40 мс.
  • Выполнение 1 с подсчетом до 100 занимает 400 мс.

Ясно, что каждый человек setTimeout должен ждать, по крайней мере, 4 мс для повторного вызова. Но это горлышко бутылки. Индивидуальная задержка на setTimeout.

Если вы запланируете 100 или более из них параллельно, то это будет просто работать.

Как мы можем оптимизировать это?

var s = new Date(),
    count = 100,
    cb = after(count, function() {
        console.log(new Date() - s);    
    }),
    array = [];

doo(count, function() {
    test(10, array, cb);
});

Установить 100 работающих параллельно на одном массиве. Это позволит избежать основного узкого места, которое является задержкой setTimeout.

Вышеуказанное завершается за 2 мс.

var s = new Date(),
    count = 1000,
    cb = after(count, function() {
        console.log(new Date() - s);    
    }),
    array = [];

doo(count, function() {
    test(1000, array, cb);
});

Завершается за 7 миллисекунд

var s = new Date(),
    count = 1000,
    cb = after(1, function() {
        console.log(new Date() - s);    
    }),
    array = [];

doo(count, function() {
    test(1000000, array, cb);
});

Выполнение 1000 заданий в параллельном режиме примерно оптимально. Но вы начнете сталкиваться с узкими местами. Подсчет до 1 миллиона все еще занимает 4500 мс.

8 голосов
/ 04 августа 2011

Ваша проблема связана с накладными расходами и единицей работы.Ваши издержки setTimeout очень высоки, в то время как ваша единица работы ar.push очень низка.Решение - старая техника оптимизации, известная как Блочная Обработка.Вместо того, чтобы обрабатывать одно UoW на вызов, вам нужно обработать блок UoW.Насколько велик «блок», зависит от того, сколько времени занимает каждое UoW и максимальное количество времени, которое вы можете потратить на каждую setTimeout / call / итерацию (до того, как пользовательский интерфейс перестанет отвечать на запросы).

function test(i, ar, callback, start){
if ( ar === undefined ){
    var ar = [],
    start = new Date;
};
if ( ar.length < i ){
    // **** process a block **** //
    for(var x=0; x<50 && ar.length<i; x++){
        ar.push( i - ( i - ar.length )  );
    }
    setTimeout(function(){
        test( i, ar, callback, start);
    },0);
}
else {
    callback(ar, start);
};
}

У вас естьобрабатывать самый большой блок, который вы можете, не вызывая проблем с пользовательским интерфейсом / производительностью.Предыдущее выполняется примерно в 50 раз быстрее (размер блока).

По той же причине мы используем буфер для чтения файла, а не для чтения его по одному байту за раз.

3 голосов
/ 28 июля 2011

Просто гипотеза ... может ли быть такой код медленным, потому что вы создаете стек рекурсии с 5000 экземплярами рекурсии?Ваш вызов не является действительно рекурсивным, поскольку он происходит через функцию settimeout, но функция, которую вы передаете ему, является замыканием, поэтому ему придется хранить все контексты замыкания ...

проблема с производительностью может быть связана со стоимостью управления памятью, и это также можно объяснить, в то время как ваш последний тест, кажется, ухудшает ситуацию ...

Я ничего не пробовал с интерпретатором, но это может бытьИнтересно посмотреть, будет ли время вычислений линейным с числом рекурсий или нет ... скажем: 100, 500, 1000, 5000 рекурсий ...

первое, что я бы попробовал, так как обходной путь не используетзакрытие:

setTimeout(test, 0, i, ar, callback, start);
0 голосов
/ 02 августа 2011

BE на самом деле говорил об этом, то, что вы используете, это рекурсивные функции, и JavaScript сейчас не имеет " рекурсивных вызовов Tail End ", что означает, что интерпретатор / механизм должен сохранятькадр стека для КАЖДОГО вызова, который становится тяжелым.

Чтобы оптимизировать решение, я бы попытался превратить его в функцию немедленного выполнения, которая вызывается в глобальной области видимости.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...