Являются ли преимущества Typed Arrays в JavaScript тем, что они работают одинаково или схожими в C?

Я играл с типизированными массивами в JavaScript.

var buffer = new ArrayBuffer(16);
var int32View = new Int32Array(buffer);

Я предполагаю, что нормальные массивы ([1, 257, true]) в JavaScript имеют низкую производительность, потому что их значения могут быть любого типа, поэтому достижение смещения в памяти не является тривиальным.

Первоначально я думал, что индексы JavaScript-массива работают так же, как объекты (поскольку они имеют много общего), и были хэш-карта, требующая поиска на основе хэша. Но я не нашел достоверной информации, подтверждающей это.

Итак, я бы предположил, почему Typed Arrays работают так хорошо, потому что они работают как обычные массивы на C, где они всегда печатаются. Учитывая пример исходного кода выше и желающий получить 10-е значение в типизированном массиве...

var value = int32View[10];
  • Тип Int32, поэтому каждое значение должно состоять из 32 бит или 4 байтов.
  • Подстрочный индекс 10.
  • Таким образом, местоположение в памяти этого значения <array offset> + (4 * 10), а затем прочитайте 4 bytes, чтобы получить общее значение.

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

Я проверил источник V8, чтобы узнать, могу ли я ответить на него сам, но мой C ржавый, и я не слишком хорошо знаком с С++.

Ответ 1

Типизированные массивы были разработаны комитетом стандартов WebGL по соображениям производительности. Как правило, массивы Javascript являются универсальными и могут содержать объекты, другие массивы и т.д. - и элементы не обязательно являются последовательными в памяти, например, они будут в C. WebGL требует, чтобы буферы были последовательными в памяти, потому что, как ожидается, их. Если Typed Arrays не используются, передача обычного массива в функцию WebGL требует большой работы: каждый элемент должен быть проверен, тип проверен, и если это правильно (например, float), затем скопируйте его в отдельный последовательный C-подобный буфер, затем передать этот последовательный буфер в C API. Ой - много работы! Для чувствительных к производительности приложений WebGL это может привести к большому падению частоты кадров.

С другой стороны, как вы предлагаете в вопросе, Typed Arrays используют последовательный C-подобный буфер уже в своей закулисной памяти. Когда вы пишете в типизированный массив, вы действительно присваиваете C-подобный массив за кулисами. Для целей WebGL это означает, что буфер может использоваться непосредственно соответствующим C API.

Обратите внимание, что вычисление адреса памяти недостаточно: браузер должен также ограничивать-проверять массив, чтобы предотвратить доступ за пределы диапазона. Это должно произойти с любым видом массива Javascript, но во многих случаях умные механизмы Javascript могут опустить проверку, когда она может доказать, что значение индекса уже находится в пределах (например, цикл от 0 до длины массива). Он также должен проверить, что индекс массива действительно является числом, а не строкой или чем-то еще! Но это по сути, как вы описываете, используя C-подобную адресацию.

НО..., что не все! В некоторых случаях умные Javascript-двигатели могут также выводить тип обычных массивов Javascript. В двигателе, таком как V8, если вы создаете обычный массив Javascript и только сохраняете в нем поплавки, V8 может оптимистично решить его как массив поплавков и оптимизировать код, который он создает для этого. Производительность может быть эквивалентна типизированным массивам. Поэтому типизированные массивы на самом деле не нужны для достижения максимальной производительности: просто используйте массивы прогнозируемо (с каждым элементом одного и того же типа), и некоторые двигатели могут также оптимизировать для этого.

Итак, почему типизированные массивы все еще должны существовать?

  • Оптимизации, такие как вывод типа массивов, действительно сложны. Если V8 выводит обычный массив, он только плавает в нем, тогда вы храните объект в элементе, он должен де-оптимизировать и регенерировать код, который снова создает массив. Это совершенно достижение, что все это работает прозрачно. Типизированные массивы намного проще: они гарантированно будут одного типа, и вы просто не можете хранить в них другие объекты, такие как объекты.
  • Оптимизация никогда не будет гарантирована; вы можете хранить только поплавки в обычном массиве, но двигатель может решить по различным причинам не оптимизировать его.
  • Тот факт, что они намного проще, означает, что другие менее сложные javascript-модули могут легко реализовать их. Им не нужна вся расширенная поддержка оптимизации.
  • Даже с действительно продвинутыми двигателями, использование оптимизаций может быть чрезвычайно сложным и иногда может быть невозможным. Типичный массив значительно упрощает уровень доказательности, который двигатель должен иметь возможность оптимизировать вокруг него. Значение, возвращаемое из типизированного массива, конечно, относится к определенному типу, и двигатели могут оптимизировать для результата, являющегося этим типом. Значение, возвращаемое из обычного массива, теоретически может иметь любой тип, и двигатель, возможно, не сможет доказать, что он всегда будет иметь тот же результат типа и, следовательно, генерирует менее эффективный код. Поэтому код вокруг типизированного массива более легко оптимизируется.
  • Типизированные массивы удаляют возможность совершить ошибку. Вы просто не можете случайно сохранить объект и внезапно получить гораздо худшую производительность.

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

Ответ 2

Да, вы в основном правы. Со стандартным массивом JavaScript механизм JavaScript должен предположить, что данные в массиве - это все объекты. Он все равно может хранить это как C-образный массив/вектор, где доступ к памяти по-прежнему как вы описали. Проблема в том, что данные - это не значение, а что-то, ссылающееся на это значение (объект).

Итак, для выполнения a[i] = b[i] + 2 требуется, чтобы движок:

  • доступ к объекту в b в индексе i;
  • проверить тип объекта,
  • извлечь значение из объекта;
  • добавить 2 к значению;
  • создать новый объект с вновь вычисленным значением из 4;
  • назначить новый объект с шага 5 в индекс i.

С типизированным массивом двигатель может:

  • получить доступ к значению в b в индексе я (включая размещение его в регистре CPU);
  • увеличить значение на 2;
  • назначить новый объект с шага 2 в индекс i.

ПРИМЕЧАНИЕ. Это не точные шаги, которые будет выполнять механизм JavaScript, поскольку это зависит от скомпилированного кода (включая окружающий код) и рассматриваемого двигателя.

Это позволяет сделать результирующие вычисления намного более эффективными. Кроме того, типизированные массивы имеют гарантию макета памяти (массивы из n-байтовых значений) и могут поэтому использоваться для непосредственного взаимодействия с данными (аудио, видео и т.д.).

Ответ 3

Когда дело доходит до производительности, все может быстро измениться. Как говорит AshleysBrain, все сводится к тому, может ли VM вывести, что обычный массив может быть реализован как типизированный массив быстро и точно. Это зависит от конкретных оптимизаций конкретной виртуальной машины JavaScript и может изменяться в любой новой версии браузера.

Этот комментарий разработчика Chrome содержит некоторые рекомендации, которые работали с июня 2012 года:

  • Нормальные массивы могут быть такими же быстрыми, как типизированные массивы, если вы выполняете много последовательного доступа. Случайный доступ за пределами массива приводит к росту массива.
  • Типизированные массивы быстро доступны для доступа, но медленнее их выделять. Если вы создаете временные массивы часто, избегайте типизированных массивов. (Исправление возможно, но это низкий приоритет.)
  • Микро-тесты, такие как JSPerf, не являются надежными для реальной работы.

Если бы я мог подробно остановиться на последнем пункте, я видел это явление с Java уже много лет. Когда вы проверяете скорость небольшого фрагмента кода, запуская его снова и снова изолированно, VM оптимизирует выходы из него. Он делает оптимизацию, которая имеет смысл только для этого конкретного теста. Ваш тест может получить стократное улучшение скорости по сравнению с запуском одного и того же кода внутри другой программы или сравнить его с запуском сразу после запуска нескольких различных тестов, которые по-разному оптимизируют один и тот же код.

Ответ 4

Я не являюсь автором javascript-движка, но имел только некоторые показания на v8, поэтому мой ответ может быть не совсем прав:

Значения Well в массивах (только нормальные массивы без отверстий/пробелов, а не разреженные. Разреженные массивы рассматриваются как объекты.) - все либо указатели, либо число с фиксированной длиной (в v8 они 32 бит, если 31 битное целое число, после чего в конце помечается бит 0, иначе это указатель).

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

И, конечно, при доступе к typedArrays определенно не существует проверки типа, которую имеет обычный массив (хотя это может быть удалено в высокоэффективном коде, который генерируется только для горячего кода).

Для записи, если он того же типа не должен быть намного медленнее. Если это другой тип, то механизм JS может генерировать для него полиморфный код, который медленнее.

Вы также можете попробовать сделать некоторые тесты на jsperf.com для подтверждения.