Производительность изменения размера массива, установка свойства длины и повторное нажатие - PullRequest
0 голосов
/ 05 ноября 2018

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

'use strict';

function addSetToArrayA(array, set) {
  for (const v of set) {
    array.push(v);
  }
}
function addSetToArrayB(array, set) {
  const origLength = array.length;
  const newLength = array.length + set.size;
  array.length = newLength;
  array[newLength - 1] = 0;
  let i = origLength;
  for (const v of set) {
    array[i++] = v;
  }
}

const set = new Set([1, 2, 3, 4, 5, 6]);

console.time('addSetToArrayA');
for (let i = 0;i<0xffffff;++i) {
  const base = [1, 2, 3, 4, 5, 6];
  addSetToArrayA(base, set);
}
console.timeEnd('addSetToArrayA');

console.time('addSetToArrayB');
for (let i = 0;i<0xffffff;++i) {
  const base = [1, 2, 3, 4, 5, 6];
  addSetToArrayB(base, set);
}
console.timeEnd('addSetToArrayB');

Результат меня немного удивил:

addSetToArrayA: 728.773ms
addSetToArrayB: 3296.437ms

Итак, я сделал еще один тест:

'use strict';

const iters = 0xfffff;

console.time('32 push');
for (let i = 0;i<iters;++i) {
  const base = [1, 2, 3, 4, 5, 6];
  for (let k = 0;k<32;++k) {
    base.push(undefined);
  }
}
console.timeEnd('32 push');

console.time('32 length');
for (let i = 0;i<iters;++i) {
  const base = [1, 2, 3, 4, 5, 6];
  base.length = 32;
}
console.timeEnd('32 length');

console.time('64 push');
for (let i = 0;i<iters;++i) {
  const base = [1, 2, 3, 4, 5, 6];
  for (let k = 0;k<64;++k) {
    base.push(undefined);
  }
}
console.timeEnd('64 push');

console.time('64 length');
for (let i = 0;i<iters;++i) {
  const base = [1, 2, 3, 4, 5, 6];
  base.length = 64;
}
console.timeEnd('64 length');

console.time('128 push');
for (let i = 0;i<iters;++i) {
  const base = [1, 2, 3, 4, 5, 6];
  for (let k = 0;k<128;++k) {
    base.push(undefined);
  }
}
console.timeEnd('128 push');

console.time('128 length');
for (let i = 0;i<iters;++i) {
  const base = [1, 2, 3, 4, 5, 6];
  base.length = 128;
}
console.timeEnd('128 length');

Результаты соответствовали тому, что я испытал ранее:

32 push: 132.061ms
32 length: 180.745ms
64 push: 284.575ms
64 length: 212.465ms
128 push: 586.747ms
128 length: 268.689ms

Например. для относительно небольших массивов изменение размера с помощью .length выполняется медленнее, чем при использовании .push(), а для больших массивов - быстрее (как и ожидалось).

Выполняет ли V8 различные типы изменения размера массива при использовании .length = ... против .push(...)? Это связано с тем, как V8 обрабатывает разреженные массивы?

Работа на узле @ 10.12 с аналогичными результатами в chrome.

1 Ответ

0 голосов
/ 05 ноября 2018

V8 разработчик здесь. Короткий ответ: .push() супероптимизирован, в то время как запись в .length - довольно медленная операция (частично из-за того, что спецификация JavaScript говорит, что она должна делать, и частично, потому что мы не оптимизировали ее достаточно сильно - но даже если бы мы это сделали, это не стало бы так быстро, как .push() для нескольких элементов).

На самом деле вы заметите аналогичную разницу между записью в .length для сокращения массива и вызовом .pop() пару раз.

Лично я считаю, что это неплохое состояние: ваш код на основе .push лаконичен, интуитивен и удобочитаем. Альтернатива, основанная на .length, выглядит как попытка выжать немного дополнительной производительности за счет того, что код станет более уродливым и сложным - разве не приятно, что это помогает , а не ? Напишите код, который вы хотите написать, позвольте движку беспокоиться о том, чтобы сделать это быстро! : -)

Выполняет ли V8 различные типы изменения размера массива при использовании .length = ... vs. .push (...)?

Запись в .length и вызов .push(...) - это очень разные операции (последняя довольно проста, первая должна выполнить кучу проверок), так что да, то, что V8 делает под капотом, обязательно отличается. Само изменение размера, если / когда это происходит, то же самое.

Это связано с тем, как V8 обрабатывает разреженные массивы?

Не в вашем примере. В общем случае запись в .length должна проверять, должен ли массив переходить в разреженный режим, но сама эта проверка выполняется довольно быстро.

...