Быстрый курс по управлению памятью

Andrey Melikhov
devSchacht
Published in
5 min readJun 19, 2017

Перевод статьи Lin Clark: A crash course in memory management. Распространяется по лицензии CC BY-SA 3.0.

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

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

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

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

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

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

Каждый из этих ящиков имеет одинаковый размер и может хранить определенное количество информации. Размер ящика зависит от архитектуры компьютера. Этот размер называется размером слова. Обычно в современных компьютерах это что-то вроде 32-битных или 64-битных слов. Но в учебных целях я собираюсь использовать размер слова 8 бит.

Следует различать следующие единицы измерения информации:

  • бит — один разряд двоичного кода;
  • байт — минимально адресуемая область памяти;
  • машинное слово — величина, равная разрядности регистров процессора и/или разрядности шины данных.

Хотя мы и привыкли к тому, что байт равен восьми битам, в ранних компьютерах существовали и другие размеры минимально-адресуемой области памяти, например 6 бит в БЭСМ-6.

В статье автор для упрощения использует размер слова равный 1 байту. На самом деле в 32-х битной архитектуре используются 4-х байтные слова, а в 64-х битной — 8-байтные слова.

Примечание переводчика.

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

Что делать, если мы хотим положить что-то, что не является числом? Например, букву H?

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

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

Автоматическое управление памятью

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

Вместо этого JavaScript-движок выступает в качестве посредника. Он управляет памятью за вас.

Скажем, какой-то JavaScript-код, например React, хочет создать переменную.

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

И он ищет в памяти место, в которое может положить это двоичное представление. Этот процесс называется выделением памяти.

Затем движок будет отслеживать, доступна ли эта переменная в программе. Если переменная больше не может быть достигнута (то есть эта переменная больше нигде не используется, — прим. пер.), память будет восстановлена, чтобы JavaScript-движок мог поместить туда новые значения.

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

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

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

Ручное управление памятью

Языки с управляемой вручную памятью очень разные. Например, давайте посмотрим, как React мог бы работать с памятью, если бы он был написан на C (что в принципе возможно теперь с WebAssembly).

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

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

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

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

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

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

О Лин Кларк

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

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

Статья на GitHub

--

--

Andrey Melikhov
devSchacht

Web-developer in big IT company Перевожу всё, до чего дотянусь. Иногда (но редко) пишу сам.