Разбираемся с ArrayBuffer и SharedArrayBuffer в JavaScript (часть 1)

260
memory-management

Перевод статьи Лин Кларк

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

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

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

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

Размер ячеек абсолютно одинаковый и все они вмещают определённое, но равное, количество информации. Их размер (т.е. вместимость) зависит от конкретного компьютера. На профессиональном языке этот размер называется «разрядность». Он обычно равен 32 или 64 битам. Но для удобства объяснения я буду использовать разрядность в 8 бит.

Если в одну из этих ячеек мы захотим записать число 2, то мы сможем с лёгкостью это сделать. Представлять цифры в двоичном исчислении совсем нетрудно.

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

Тогда нам понадобится способ представить её в виде числа. Чтобы это сделать, нам нужна будет кодировка, например, такая, как UTF-8. Но тогда нам также потребуется и нечто, что может превратить букву в число… к примеру, дешифрующее кольцо. И теперь мы можем её сохранить.

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

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

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

Рассмотрим ситуацию, когда некий JavaScript код (назовём его React) хочет создать переменную.

В этом случае движок JavaScript пропускает требуемое значение через кодировщик и в результате получает его двоичное представление.

А также он находит в памяти свободное место, куда это полученное представление может поместиться. Такой процесс называют «выделение памяти».

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

Такой процесс контроля записываемых в память переменных (объектов, текстовых строк и других разновидностей значений), а также процесс их удаления, когда доступ к ним прекращается, называется «сборка мусора».

Языки вроде JavaScript, в которых память нельзя контролировать напрямую через код, носят описательное название «языки с автоматическим управлением памятью».

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

Ручное управление памятью
От описанных выше кардинально отличаются языки с ручным управлением памятью. К примеру, давайте посмотрим, как бы наш код под названием React работал бы с памятью, если бы был написан на языке С (что с появлением WebAssembly сейчас возможно).

У языка С нет той степени абстракции, которая разделяет человека и память в JavaScript, то есть, разработчик может работать с памятью напрямую. Он может самостоятельно что-то извлекать из памяти и что-то в неё записывать.

Когда вы компилируете С или любой другой язык в WebAssembly, то инструмент, который вы используете, добавит в ваш WebAssembly вспомогательный код. Например, он может добавить код, который кодирует и декодирует байты. Такой код называется «рабочей средой». Эта рабочая среда помогает делать то же самое, что движок JavaScript делает для своего языка.

С одним только исключением: среда выполнения у языков с ручным управлением памятью не занимается сборкой мусора.

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

Чтобы среда выполнения нашла для вас подходящие адреса памяти, вы можете использовать функцию malloc (сокр. от англ. «memory allocate»). Таким образом, вы получите место для записи своих данных и эти адреса будут удалены из списка свободной памяти. Когда вы закончите с ними работать, вам нужно будет стереть данные, после чего эти адреса вновь окажутся в списке свободной памяти.

Но вам придётся постоянно думать о том, когда вызывать эти функции. Отсюда и название «ручное управление памятью» – то есть, вы управляете памятью самостоятельно.

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

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