devSchacht
Published in

devSchacht

Иллюстрированное введение в ArrayBuffers и SharedArrayBuffers

Перевод статьи Lin Clark: A cartoon intro to ArrayBuffers and SharedArrayBuffers. Распространяется по лицензии CC BY-SA 3.0.

Это вторая статья в серии из трех частей:

  1. Быстрый курс по управлению памятью
  2. Иллюстрированное введение в ArrayBuffers и SharedArrayBuffers
  3. Избегаем состояния гонки в SharedArrayBuffers с помощью Atomics

В прошлой статье я объяснила, как управляемые памятью языки, такие как JavaScript, работают с ОЗУ. Я также рассказала, как работает ручное управление памятью в таких языках, как C.

Почему это важно, когда мы говорим об ArrayBuffers и SharedArrayBuffers?

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

Почему это то, что вам хотелось бы сделать?

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

Например, когда вы создаете переменную в JavaScript, движок должен угадать, какого типа эта переменная и как она должна быть представлена в памяти. Поскольку это только догадки, JavaScript-движок обычно резервирует больше места, чем это действительно нужно для переменной. В зависимости от переменной слот памяти может быть в 2–8 раз больше, чем нужно, что может привести к большому количеству потерянной памяти.

Кроме того, определенные шаблоны создания и использования JavaScript-объектов могут затруднить сборку мусора. Если вы занимаетесь ручным управлением памятью, вы можете выбрать стратегию выделения и освобождения памяти, которая больше подходит для вашего конкретного случая.

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

Но для тех случаев, когда вам нужно работать на низком уровне, чтобы сделать ваш код настолько быстрым, насколько это возможно, ArrayBuffers и SharedArrayBuffers дают вам эту возможность.

Итак, как работает ArrayBuffer?

Это похоже на работу с любым другим массивом в JavaScript. За исключением того, что при использовании ArrayBuffer вы не можете вставлять в него какие-либо JavaScript-типы, например объекты или строки. Единственное, что вы можете вставить в него, это байты (которые вы можете представить с помощью чисел).

Здесь я хочу прояснить, что вы фактически не добавляете этот байт непосредственно в ArrayBuffer. Сам по себе этот ArrayBuffer не знает, насколько большой должен быть байт или как различные типы чисел должны быть преобразованы в байты.

Сам массив ArrayBuffer - это всего лишь кучка нулей и единиц в одной линии. ArrayBuffer не знает, где заканчивается первый и начинается второй элемент в этом массиве.

Чтобы обеспечить контекст, чтобы разбить его на ящики, нам нужно обернуть его в так называемое представление. Эти представления данных могут быть добавлены с помощью типизированных массивов, и есть много разных видов типизированных массивов, с которыми ArrayBuffer может работать.

Например, у вас может быть массив типа Int8, разбивающий ArrayBuffer на 8-битные байты.

Или у вас может быть массив беззнаковых Int16, разбивающий ArrayBuffer на 16-битные кусочки, а также обрабатывающий значения так, как если бы это были целые числа без знака.

Вы можете даже иметь несколько представлений в одном базовом буфере. Различные представления дают вам разные результаты для одних и тех же операций.

Например, если мы получим элементы 0 и 1 из представления Int8 в массиве ArrayBuffer, это даст нам отличные значения от элемента 0 в представлении Uint16, хотя они содержат те же самые биты.

Таким образом, ArrayBuffer в основном работает как необработанная память. Он эмулирует вид прямого доступа к памяти, который вы имели бы на языке, подобном C.

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

Итак, что такое SharedArrayBuffer?

Чтобы объяснить SharedArrayBuffers, мне нужно немного рассказать о параллельном исполнении кода и JavaScript.

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

В типичном приложении все работы производятся одним индивидуумом — основным потоком (main thread). Я рассказывала об этом раньше… основной поток похож на разработчика полного стека. Он отвечает за JavaScript, DOM и макет.

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

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

В большинстве языков программирования способ, которым вы обычно разделяете работу — это использование некой сущности, называемой потоком. Это похоже на то, как несколько человек работают над проектом. Если у вас есть задачи, которые довольно независимы друг от друга, вы можете назначить их в потоки. Затем оба этих потока могут работать над своими отдельными задачами одновременно.

В JavaScript можно распараллелить задачи с помощью использования веб-воркеров. Эти веб-вебворкеры немного отличаются от потоков, которые вы используете в других языках. По умолчанию они не обмениваются памятью.

Это означает, что если вы хотите поделиться некоторыми данными с другим потоком, вы должны их скопировать. Это делается с помощью функции postMessage.

postMessage принимает любой объект, который вы вставляете в него, сериализует его и передает его другому веб-воркеру, где он десериализуется и помещается в память.

Это довольно медленный процесс.

Для некоторых видов данных, таких как ArrayBuffers, вы можете выполнять так называемую передачу памяти. Это означает перемещение этого определенного блока памяти так, чтобы другой веб-воркер получил к нему доступ.

Но тогда первый веб-воркер больше не будет иметь доступа к нему.

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

Это то, что вам дает SharedArrayBuffers.

С SharedArrayBuffer оба веб-воркера, оба потока, могут записывать и считывать данные из одного и того же фрагмента памяти.

Это означает, что у них нет коммуникационных накладных расходов и задержек, возникающих при использовании postMessage. Оба веб-воркера имеют мгновенный доступ к данным.

В то же время существует определенная опасность наличия этого немедленного доступа из обоих потоков. Это может привести к так называемому состояниям гонки (race conditions).

Я расскажу об этом больше в следующей статье.

Каков текущий статус SharedArrayBuffers?

SharedArrayBuffers скоро появятся во всех основных браузерах.

Они уже есть в Safari (в Safari 10.1). Firefox и Chrome добавят их в своих июльских/августовских релизах. Edge планирует добавить их с осенним обновлением Windows.

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

Мы ожидаем, что разработчики JavaScript-библиотек будут создавать библиотеки, предоставляющие вам простую и безопасную работу с SharedArrayBuffers.

Кроме того, когда SharedArrayBuffers встроены в платформу, WebAssembly может использовать их для реализации поддержки потоков. Как только это будет сделано, вы сможете использовать абстракции для управления потоками на языках, подобных Rust, заявляющего безопасное управление потоками как одну из своих главных задач.

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

О Лин Кларк

Лин работает инженером в команде Mozilla Developer Relations. Она занимается JavaScript, WebAssembly, Rust и Servo, а также рисует комиксы про то, как работает наш код.

Слушайте наш подкаст в iTunes и SoundCloud, читайте нас на Medium, контрибьютьте на GitHub, общайтесь в группе Telegram, следите в Twitter и канале Telegram, рекомендуйте в VK и Facebook.

Статья на GitHub

--

--

Подкаст. Переводы. Веб-разработка.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store