Работа с Javascript TypedArray
Почему TypedArrays не быстрее обычных массивов? Я хочу использовать значения precalc для CLZ (вычислить ведущую нулевую функцию). И я не хочу, чтобы они интерпретировали как обычные объекты?
http://jsperf.com/array-access-speed-2/2
Код подготовки:
Benchmark.prototype.setup = function() {
var buffer = new ArrayBuffer(0x10000);
var Uint32 = new Uint32Array(buffer);
var arr = [];
for(var i = 0; i < 0x10000; ++i) {
Uint32[i] = (Math.random() * 0x100000000) | 0;
arr[i] = Uint32[i];
}
var sum = 0;
};
Тест 1:
sum = arr[(Math.random() * 0x10000) | 0];
Тест 2:
sum = Uint32[(Math.random() * 0x10000) | 0];
![enter image description here]()
PS Могут быть мои перфекционные тесты недействительными, не стесняйтесь меня исправлять.
Ответы
Ответ 1
var buffer = new ArrayBuffer(0x10000);
var Uint32 = new Uint32Array(buffer);
- это не то же самое, что:
var Uint32 = new Uint32Array(0x10000);
не из-за нового ArrayBuffer (вы всегда получаете буфер массива: см. Uint32.buffer в обоих случаях), но из-за параметра длины: с ArrayBuffer у вас есть 1 байт на элемент, а Uint32Array - 4 байта на элемент.
Итак, в первом случае (и в вашем коде) Uint32.length = 0x1000/4 и ваши петли выходят за пределы 3 из 4 раз. Но, к сожалению, вы никогда не получите ошибок, только плохие показатели.
Используя новый ArrayBuffer, вы должны объявить Uint32 следующим образом:
var buffer = new ArrayBuffer(0x10000 * 4);
var Uint32 = new Uint32Array(buffer);
Смотрите jsperf с (0x10000) и jsperf с (0x10000 * 4).
Ответ 2
Современные двигатели будут использовать истинные массивы за кулисами, даже если вы используете Array
, если они думают, что могут, отступая от массивов карт свойств, если вы делаете что-то, что заставляет их думать, что они не могут использовать истинный массив.
Также обратите внимание, что в качестве radsoc указывает, var buffer = new ArrayBuffer(0x10000)
, тогда var Uint32 = new Uint32Array(buffer)
создает массив Uint32, размер которого равен 0x4000 (0x10000/4), а не 0x10000, потому что значение, которое вы даете ArrayBuffer
, находится в байтах, но, конечно, есть четыре байта на запись Uint32Array. Все ниже использует new Uint32Array(0x10000)
вместо этого (и всегда делал это, даже до этого редактирования), чтобы сравнивать яблоки с яблоками.
Итак, начнем с new Uint32Array(0x10000)
: http://jsperf.com/array-access-speed-2/11 (к сожалению, JSPerf потерял это тест и его результаты, и теперь полностью отключен)
![graph showing roughly equivalent performance]()
Это говорит о том, что, поскольку вы заполняете массив простым и предсказуемым способом, современный движок продолжает использовать истинный массив (с его преимуществами по производительности) под обложками, а не смену. Мы видим в основном такую же производительность для обоих. Разница в скорости может относиться к преобразованию типа, принимающего значение Uint32
и присваивая его sum
как number
(хотя я удивлен, если преобразование этого типа не отложено...).
Добавьте некоторый хаос:
var Uint32 = new Uint32Array(0x10000);
var arr = [];
for (var i = 0x10000 - 1; i >= 0; --i) {
Uint32[Math.random() * 0x10000 | 0] = (Math.random() * 0x100000000) | 0;
arr[Math.random() * 0x10000 | 0] = (Math.random() * 0x100000000) | 0;
}
var sum = 0;
... так что движок должен опуститься на старомодные карты свойств "массивы", и вы видите, что типизированные массивы заметно превосходят старомодный вид: http://jsperf.com/array-access-speed-2/3 (к сожалению, JSPerf проиграл этот тест и его результаты)
![bar graph showing marked performance improvement for typed arrays]()
Умные, эти инженеры-разработчики JavaScript...
Конкретная вещь, которую вы делаете с природой массива Array
не-массива, имеет значение; рассмотреть следующие вопросы:
var Uint32 = new Uint32Array(0x10000);
var arr = [];
arr.foo = "bar"; // <== Non-element property
for (var i = 0; i < 0x10000; ++i) {
Uint32[i] = (Math.random() * 0x100000000) | 0;
arr[i] = (Math.random() * 0x100000000) | 0;
}
var sum = 0;
Это все еще заполняет массив предсказуемо, но мы добавляем к нему неэлементное свойство (foo
). http://jsperf.com/array-access-speed-2/4 (к сожалению, JSPerf потерял это испытание и его результаты) По-видимому, двигатели довольно умны, и сохраните это неэлементное свойство в стороне, продолжая использовать истинный массив для свойств элемента:
![bar graph showing performance improvement for standard arrays when <code>Array</code> array gets non-element property]()
Я немного потерял объяснение, почему стандартные массивы должны быть быстрее там по сравнению с нашим первым тестом выше. Погрешность измерения? Капризы в Math.random
? Но мы все еще уверены, что данные в массиве в Array
по-прежнему являются истинным массивом.
Если мы делаем то же самое, но заполняем обратный порядок:
var Uint32 = new Uint32Array(0x10000);
var arr = [];
arr.foo = "bar"; // <== Non-element property
for (var i = 0x10000 - 1; i >= 0; --i) { // <== Reverse order
Uint32[i] = (Math.random() * 0x100000000) | 0;
arr[i] = (Math.random() * 0x100000000) | 0;
}
var sum = 0;
... мы вернемся к типизированным массивам, победив — кроме IE11: http://jsperf.com/array-access-speed-2/9 (к сожалению, JSPerf потерял этот тест и его результаты)
![graph showing typed arrays winning except on IE11]()
Ответ 3
В вашем случае причина плохой производительности - вы пытаетесь читать вне массива при использовании Uint32Array из-за ошибки с длиной массива.
Но если это не будет реальной причиной, то:
Попробуйте использовать Int32Array вместо Uint32Array. Я думаю, что в V8 переменные не могут иметь тип uint32, но могут иметь int32/double/pointer. Поэтому, когда вы назначаете тип uint32 переменной, он будет преобразован в более медленный double.
Если вы используете 32-битную версию V8, тогда переменные могут иметь тип int31/double/pointer. Таким образом, int32 также будет преобразован в double. Но если вы используете обычный массив, а все значения - int31, тогда преобразование не требуется, поэтому обычный массив может быть быстрее.
Также использование int16 может потребовать некоторых преобразований для получения int32 (из-за дополнения знака и одного).
Uint16 не требует преобразования, поскольку V8 может просто добавить нули влево.
PS. Вам могут быть интересны указатели и int31 (или int32 на x64) - это те же самые вещи в V8. Это также означает, что int32 потребует 8 байтов на x64. Кроме того, это причина, по которой на x86 нет типа int32: поскольку, если бы мы использовали все 32 бита для хранения целого числа, у нас больше не было бы места для сохранения указателей.