Pull to refresh
2461.79
RUVDS.com
VDS/VPS-хостинг. Скидка 15% по коду HABR15

ArrayBuffer и SharedArrayBuffer в JavaScript, часть 2: знакомство с новыми объектами языка

Reading time 7 min
Views 18K
Original author: Lin Clark
В прошлый раз мы, в качестве подготовки к разговору об ArrayBuffer и SharedArrayBuffer, рассмотрели разные подходы к управлению памятью. Как вы, должно быть, помните, JS-движок играет роль посредника при работе с памятью, однако, новые объекты дают программисту некоторые ручные инструменты. Для чего это может понадобиться?

image

JavaScript и управление памятью


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


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

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

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

Во многих случаях, однако, автоматическое управление памятью особых проблем не вызывает. Большинство JS-приложений не настолько требовательны к производительности, чтобы заставить программистов всерьёз задумываться о ручном управлении памятью. И, во многих случаях, ручное управление памятью даже может негативно сказаться на производительности, по меньшей мере — на производительности труда программиста.

Однако, в тех случаях, когда в приоритете — скорость работы программы, имеет смысл взглянуть в сторону ArrayBuffer и SharedArrayBuffer.


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

Как работает ArrayBuffer


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


Обычный массив, слева, может содержать числа, объекты, строки, и так далее. ArrayBuffer может содержать лишь байты

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

ArrayBuffer можно представить в виде последовательности нулей и единиц, записанных в одну строку. Этот объект не осведомлён о том, где находятся границы элементов.


ArrayBuffer как последовательность нулей и единиц

Для того, чтобы сырые данные из ArrayBuffer можно было воспринимать в виде чего-то осмысленного, чтобы разбить поток нулей и единиц на блоки, нужно обернуть его в объект DataView или в типизированный массив.

Например, в качестве обёртки можно использовать массив типа Int8Array, что приведёт к тому, что то, что хранится в ArrayBuffer, будет разбито на 8-битные фрагменты.


Последовательность нулей и единиц разбита на блоки по 8 бит

Если применить в подобной ситуации типизированный массив Uint16Array, это приведёт к тому, что содержимое ArrayBuffer будет разбито на фрагменты по 16 бит, и к тому, что восприниматься эти данные будут как беззнаковые целые числа.


Последовательность нулей и единиц разбита на блоки по 16 бит

Один и тот же ArrayBuffer может служить основой для нескольких обёрток. При таком подходе одни и те же операции над ним дадут разные результаты.

Например, ниже показано, что первый и второй элементы ArrayBuffer, доступ к которому осуществляется через массив типа Int8Array, отличаются от первого и второго элемента, взятых при работе с ArrayBuffer посредством массива Uint16Array. Одна и та же последовательность битов, при работе с ней через структуры данных разных типов, выглядит по-разному.


Доступ к одному и тому же ArrayBuffer с использованием разных обёрток

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

Возможно, вы зададитесь вопросами о том, зачем нужен дополнительный уровень абстракции над ArrayBuffer, о том, не лучше ли дать JS-программистам прямой доступ к памяти. Всё сделано именно так из-за того, что прямой доступ к памяти может стать источником проблем с безопасностью.

Что такое SharedArrayBuffer


Для того, чтобы говорить о том, что такое SharedArrayBuffer, сначала надо разобраться с тем, как в JavaScript осуществляется параллельное выполнение кода.

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

В типичном JS-приложении всю работу выполняет главный поток. Он напоминает разработчика полного цикла, который отвечает и за код на JavaScript, и за DOM, и за макет веб-страницы.

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


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

Однако, иногда снизить объём работы главного потока недостаточно. Случается так, что главному потоку нужен помощник.

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

В JavaScript подобное реализуется с помощью Web Workers API. В частности, речь идёт об объекте типа worker, представляющего собой средство для работы с фоновыми задачами. Фоновые задачи в JS слегка отличаются от потоков и процессов из других языков. В частности, по умолчанию у них нет общей памяти.


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

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

Функция postMessage сериализует передаваемые ей объекты, передаёт в фоновую задачу, которой они предназначены, там они десериализуются и записываются в память.


Функция postMessage позволяет организовать обмен данными между потоками

Передача данных между потоками таким способом — процесс довольно медленный.

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


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

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

Именно эту возможность и даёт JS-разработчикам SharedArrayBuffer.


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

Благодаря SharedArrayBuffer оба потока, обе фоновых задачи, представленные объектами worker, могут читать и писать данные в одну и ту же область памяти.

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

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


Гонка потоков, имеющих доступ к одному и тому же участку памяти

Подробнее о состоянии гонки потоков мы поговорим в третьей части этой серии материалов.

Поддержка SharedArrayBuffer


Известно, что в ближайшее время самые популярные браузеры будут оснащены поддержкой SharedArrayBuffer.


Браузеры приветствуют SharedArrayBuffer

Поддержка SharedArrayBuffer уже имеется в Safari (10.1). На очереди — Firefox и Chrome. Они, предположительно,  обзаведутся поддержкой этого нового объекта к концу лета. В Edge, вероятнее всего, SharedArrayBuffer появится осенью.

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

Хочется надеяться, что новые возможности SharedArrayBuffer заинтересуют разработчиков JS-библиотек, которые создадут простые и безопасные средства, пользуясь которыми обычные разработчики смогут применять SharedArrayBuffer в своих проектах.

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

Итоги


Как видите, SharedArrayBuffer способен вывести взаимодействие параллельных процессов в JS на новый уровень, и, вместе с ArrayBuffer, дать разработчикам некоторые возможности по ручному управлению памятью.

В следующий раз мы поговорим о средствах, которые разработчики библиотек могут использовать при создании инструментов для многопоточной работы с данными на основе SharedArrayBuffer. В частности, речь пойдёт об объекте JS Atomics.

Уважаемые читатели! Какие варианты применения в своих проектах ArrayBuffer и SharedArrayBuffer вы видите?
Tags:
Hubs:
+17
Comments 5
Comments Comments 5

Articles

Information

Website
ruvds.com
Registered
Founded
Employees
11–30 employees
Location
Россия
Representative
ruvds