Какой самый быстрый способ перебрать массив в JavaScript? - PullRequest
221 голосов
/ 18 марта 2011

Я узнал из книг, что вы должны написать для цикла, как это:

for(var i=0, len=arr.length; i < len; i++){
    // blah blah
}

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

Другие говорят, что компилятор сделает некоторую оптимизацию для этого, так что вы можете просто написать:

for(var i=0; i < arr.length; i++){
    // blah blah
}

Я просто хочу знать, какой способ лучше всего применять на практике?

Ответы [ 22 ]

311 голосов
/ 31 августа 2011

После выполнения этого теста в большинстве современных браузеров ...

http://jsben.ch/y3SpC

В настоящее время , самая быстрая форма цикла (ина мой взгляд, наиболее синтаксически очевидный).

стандарт для цикла с кэшированием длины

for (var i = 0, len = myArray.length; i < len; i++) {

}

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

83 голосов
/ 18 марта 2011

Абсолютно быстрый способ циклического перемещения по массиву JavaScript:

var len = arr.length;
while (len--) {
    // blah blah
}

См. http://blogs.oracle.com/greimer/entry/best_way_to_code_a для полного сравнения

33 голосов
/ 18 июня 2016

По состоянию на июнь 2016 года , проводим некоторые тесты в последней версии Chrome (71% рынка браузеров в мае 2016 года и растет):

  • Самый быстрый циклявляется циклом for , как с длиной кэширования, так и без нее, обеспечивая действительно одинаковую производительность.(Цикл for с длиной в кэше иногда дает лучшие результаты, чем цикл без кеширования, но разница почти ничтожна, что означает, что механизм может быть уже оптимизирован в соответствии со стандартом и, вероятно, наиболее прост для цикла без кэширования).
  • Цикл while с декрементами был примерно в 1,5 раза медленнее, чем цикл for.
  • Цикл, использующий функцию обратного вызова (например, стандарт для forEach), был примерно в 10 раз медленнее, чем цикл for.

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

  • Если ваше приложение выполняет итерации по большому количеству элементов или ваш код цикла находится внутри функции, которая часто используется, ответом является простой цикл for:

    for (var i = 0; i < arr.length; i++) {
      // Do stuff with arr[i] or i
    }
    
  • Если ваше приложение на самом деле не перебирает множество элементов или вам просто нужно делать небольшие итерации здесь и там, используя стандартный обратный вызов forEach или любую аналогичную функцию из вашей библиотеки JSвыбор может быть более понятным и менее подверженным ошибкам, поскольку область видимости переменной индекса закрыта и вам не нужно использовать скобки, получая прямой доступ к значению массива:

    arr.forEach(function(value, index) {
      // Do stuff with value or index
    });
    
  • Если вына самом деле нужно потратить несколько миллисекунд, итерируя по миллиардам строк, и длина вашего массива не изменяется в процессе, вы можете рассмотреть кэширование длины в цикле for.Хотя я думаю, что сейчас это действительно не нужно:

    for (var i = 0, len = arr.length; i < len; i++) {
      // Do stuff with arr[i]
    }
    
28 голосов
/ 18 марта 2011

Если порядок не важен, я предпочитаю этот стиль:

for(var i = array.length; i--; )

Он кэширует длину и намного короче для записи.Но он будет перебирать массив в обратном порядке.

22 голосов
/ 05 марта 2018

Это просто 2018 год, поэтому обновление может быть приятным ...

И я действительно должен не согласиться с принятым ответом .Это зависит от разных браузеров.некоторые делают forEach быстрее, некоторые for-loop, а некоторые while, вот эталонный тест по всему методу http://jsben.ch/mW36e

arr.forEach( a => {
  // ...
}

, и так как вы можете увидеть много циклов типа for(a = 0; ... )тогда стоит отметить, что без переменных 'var' будут определены глобально, и это может существенно повлиять на скорость, поэтому она будет медленной.

var arr = arr = new Array(11111111).fill(255);
var benches =     
[ [ "empty", () => {
  for(var a = 0, l = arr.length; a < l; a++);
}]
, ["for-loop", () => {
  for(var a = 0, l = arr.length; a < l; ++a)
    var b = arr[a] + 1;
}]
, ["for-loop++", () => {
  for(var a = 0, l = arr.length; a < l; a++)
    var b = arr[a] + 1;
}]
, ["for-loop - arr.length", () => {
  for(var a = 0; a < arr.length; ++a )
    var b = arr[a] + 1;
}]
, ["reverse for-loop", () => {
  for(var a = arr.length - 1; a >= 0; --a )
    var b = arr[a] + 1;
}]
,["while-loop", () => {
  var a = 0, l = arr.length;
  while( a < l ) {
    var b = arr[a] + 1;
    ++a;
  }
}]
, ["reverse-do-while-loop", () => {
  var a = arr.length - 1; // CAREFUL
  do {
    var b = arr[a] + 1;
  } while(a--);   
}]
, ["forEach", () => {
  arr.forEach( a => {
    var b = a + 1;
  });
}]
, ["for..in (only 3.3%)", () => {
  var ar = arr.slice(0,arr.length/33);
  for( const a in ar ) {
    var b = a + 1;
  }
}]
, ["Duff's device", () => {
  var i = 0;
  var r = arr.length % 8;
  var n = (arr.length - r) / 8;
  if (r > 0) do {
      var b = arr[i++] + 1;
    }
    while (--r);
  if (n > 0) do {
      var b = arr[i] + 1;
      var c = arr[i+1] + 1;
      var d = arr[i+2] + 1;
      var e = arr[i+3] + 1;
      var f = arr[i+4] + 1;
      var g = arr[i+5] + 1;
      var h = arr[i+6] + 1;
      var k = arr[i+7] + 1;
      i = --n >>> 3;
    }
    while (n);
}]
, ["Duff's device negative", () => {
  var r = arr.length % 8;
  var n = (arr.length-r) / 8; ///Math.floor(arr.length / 8);
  var i	= arr.length ; // -1;

  while(r){
    var b = arr[--i] + 1;
    --r;
  }

  while(n){
      var b = arr[i] + 1;
      var c = arr[i-1] + 1;
      var d = arr[i-2] + 1;
      var e = arr[i-3] + 1;
      var f = arr[i-4] + 1;
      var g = arr[i-5] + 1;
      var h = arr[i-6] + 1;
      var j = arr[i-7] + 1;
      i = --n >>> 3;
  }
}]];
function bench(title, f) {
  var t0 = performance.now();
  var res = f();
  return performance.now() - t0; // console.log(`${title} took ${t1-t0} msec`);
}
var globalVarTime = bench( "for-loop without 'var'", () => {
  // Here if you forget to put 'var' so variables'll be global
  for(a = 0, l = arr.length; a < l; ++a)
     var b = arr[a] + 1;
});
var times = benches.map( function(a) {
                      arr = new Array(11111111).fill(255);
                      return [a[0], bench(...a)]
                     }).sort( (a,b) => a[1]-b[1] );
var max = times[times.length-1][1];
times = times.map( a => {a[2] = (a[1]/max)*100; return a; } );
var template = (title, time, n) =>
  `<div>` +
    `<span>${title} &nbsp;</span>` +
    `<span style="width:${3+n/2}%">&nbsp;${Number(time.toFixed(3))}msec</span>` +
  `</div>`;

var strRes = times.map( t => template(...t) ).join("\n") + 
            `<br><br>for-loop without 'var' ${globalVarTime} msec.`;
var $container = document.getElementById("container");
$container.innerHTML = strRes;
body { color:#fff; background:#333; font-family:helvetica; }
body > div > div {  clear:both   }
body > div > div > span {
  float:left;
  width:43%;
  margin:3px 0;
  text-align:right;
}
body > div > div > span:nth-child(2) {
  text-align:left;
  background:darkorange;
  animation:showup .37s .111s;
  -webkit-animation:showup .37s .111s;
}
@keyframes showup { from { width:0; } }
@-webkit-keyframes showup { from { width:0; } }
<div id="container"> </div>
19 голосов
/ 02 июня 2014

2014 While вернулся

Просто думай логично.

Посмотрите на это

for( var index = 0 , length = array.length ; index < length ; index++ ) {

 //do stuff

}
  1. Необходимо создать как минимум 2 переменные (индекс, длина)
  2. Необходимо проверить, меньше ли индекс длины
  3. Нужно увеличить индекс
  4. цикл for имеет 3 параметра

Теперь скажите мне, почему это должно быть быстрее, чем:

var length = array.length;

while( --length ) { //or length--

 //do stuff

}
  1. Одна переменная
  2. Без проверок
  3. индекс уменьшен (машины так предпочитают)
  4. while имеет только один параметр

Я был совершенно сбит с толку, когда Chrome 28 показал, что цикл for быстрее, чем время. Это должно быть бен какой-то

"Ну, все используют цикл for, давайте сосредоточимся на этом, когда разработка для хрома. "

Но теперь, в 2014 году, цикл while вернулся на хром. это в 2 раза быстрее, в других / старых браузерах оно всегда было быстрее.

В последнее время я сделал несколько новых тестов. Теперь в реальном мире эти короткие коды ничего не стоят, и jsperf не может правильно выполнить цикл while, потому что он должен воссоздать array.length, что также требует времени.

вы не можете получить фактическую скорость цикла while на jsperf.

вам нужно создать свою собственную функцию и проверить это с помощью window.performance.now()

И да ... цикл пока просто не быстрее.

Настоящая проблема на самом деле - манипуляция домом / время рендеринга время рисования или как хочешь его назвать.

Например, у меня есть сцена холста, где мне нужно вычислить координаты и столкновения ... это делается между 10-200 микросекундами (не миллисекундами). для рендеринга всего требуется несколько миллисекунд. То же, что и в DOM.

НО

Существует еще один супер производительный способ использования for loop в некоторых случаях ... например, для копирования / клонирования массива

for(
 var i = array.length ;
 i > 0 ;
 arrayCopy[ --i ] = array[ i ] // doing stuff
);

Обратите внимание на настройку параметров:

  1. То же, что и в цикле while, я использую только одну переменную
  2. Необходимо проверить, является ли индекс больше 0;
  3. Как вы можете видеть, этот подход отличается от обычного цикла for, который все используют, так как я делаю вещи внутри 3-го параметра, а также уменьшаю непосредственно внутри массива.

Сказал, что это подтверждает, что такие машины, как -

написав, что я думаю сделать это немного короче и удалить некоторые ненужные вещи, и написал это, используя тот же стиль:

for(
 var i = array.length ;
 i-- ;
 arrayCopy[ i ] = array[ i ] // doing stuff
);

Даже если это короче, похоже, что использование i еще раз замедляет все. Это на 1/5 медленнее, чем предыдущий for цикл и while один.

Примечание: ; очень важно после for Looo без {}

Даже если я только что сказал вам, что jsperf - не лучший способ тестирования скриптов .. я добавил эти 2 цикла здесь

http://jsperf.com/caching-array-length/40

А вот еще один ответ о производительности в javascript

https://stackoverflow.com/a/21353032/2450730

Этот ответ показывает эффективные способы написания javascript. Поэтому, если вы не можете прочитать это, спросите, и вы получите ответ или прочитаете книгу о javascript http://www.ecma -international.org / ecma-262 / 5.1 /

11 голосов
/ 17 октября 2011

http://jsperf.com/caching-array-length/60

В последней редакции теста, которую я подготовил (повторное использование старой версии), показана одна вещь.

Длина кэширования не так уж важна, ноэто не вредит.

Каждый первый запуск теста, указанного выше (на недавно открытой вкладке), дает наилучшие результаты для последних 4 фрагментов (3-й, 5-й, 7-й и 10-й в диаграммах) в Chrome,Opera и Firefox в моем Debian Squeeze 64-bit ( мое настольное оборудование ).Последующие прогоны дают совершенно другой результат.

Выводы в отношении производительности просты:

  • Переходите к циклу for (forward) и тестируйте, используя !== вместо <.
  • Если вам не нужно повторно использовать массив позже, тогда эффективен цикл на уменьшенной длине и деструктивный shift() -ing массив.

tl; dr

В настоящее время (2011.10) приведенный ниже шаблон выглядит самым быстрым.

for (var i = 0, len = arr.length; i !== len; i++) {
    ...
}

Помните, что кэширование arr.length здесь не критично,так что вы можете просто проверить на i !== arr.length и производительность не упадет, но вы получите более короткий код.


PS: я знаю, что во фрагменте с shift() вместо него можно использовать его результатдоступа к 0-му элементу, но я как-то упустил это из виду после повторного использования предыдущей ревизии (которая имела неправильные циклы while), а позже я не хотел терять уже полученные результаты.

8 голосов
/ 10 июня 2013

«Лучший» как в чистом исполнении?или производительность И удобочитаемость?

Чистая производительность "лучшая" - это, которая использует кэш и оператор префикса ++ (мои данные: http://jsperf.com/caching-array-length/189)

for (var i = 0, len = myArray.length; i < len; ++i) {
  // blah blah
}

Я бы сказал, что цикл без кеша - лучший баланс во времени выполнения и времени чтения программистом. Каждый программист, начавший с C / C ++ / Java, не будет тратить мс на чтение этого

for(var i=0; i < arr.length; i++){
  // blah blah
}
7 голосов
/ 10 ноября 2014

** кэширует длину массива внутри цикла, некоторые секунды времени будут исключены.Зависит от элементов в массиве, если в массиве есть больше элементов, есть существенная разница в отношении времени M *

**

sArr; //Array[158];

for(var i = 0 ; i <sArr.length ; i++) {
 callArray(sArr[i]); //function call
}

***end: 6.875ms***

**

**

sArr; //Array[158];
for(var i = 0,len = sArr.length ; i < len ; i++) {
  callArray(sArr[i]); //function call
}

***end: 1.354ms***

**

6 голосов
/ 19 июня 2015

Этот выглядит самым быстрым способом на сегодняшний день ...

var el;
while (el = arr.shift()) {
  el *= 2;
}

Примите во внимание, что это будет использовать массив, съедая его и ничего не оставляя ...

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