Сколько памяти занимает массив
В языке С++ определены только одномерные массивы, но поскольку элементом массива может быть массив , возможно определить и двумерные массивы. Они определяются списком константных-выражений следующих за идентификатором массива, причем каждое константное- выражение заключается в свои квадратные скобки. Каждое константное- выражение в квадратных скобках определяет число элементов по данному измерению массива, так что объявление двумерного массива содержит два константных-выражения, трехмерного – три и т.д.
Объем занимаемой памяти в байтах для двухмерного массива вычисляется по формуле:
Если мы имеем дело с двумерным массивом B размерности MxN , расположенным в памяти по строкам, то адрес элемента B[i][j] вычисляется по формуле:
Так как массивы занимают непрерывный участок памяти, то двумерный массив размерности MxN можно рассматривать как одномерный массив из M указателей, которые являются константами. Константы -указатели содержат значения адресов M одномерных безымянных массивов. Поэтому обращение к элементу B[i][j] посредством B[i*N + j] невозможно, так как указателя с номером i*N + j может не существовать.
Пример 1. Определение размера памяти двумерного массива
Результат выполнения программы:
i_mas[10][10] занимает 400 байт – 4 байта (тип int ) * 10*10 (количество элементов массива)
f_mas[3][5]=,,> занимает 60 байт – 4 байта (тип float ) * 3*5 (объявленное количество элементов массива)
d_mas[2*q-r] [2*v/p] занимает 112 байт – 8 байт (тип double ) * 7*2 (вычисленное через формулу количество элементов массива)
r_mas[][3]=,,> занимает 36 байт – 4 байта (тип int ) * 3*3 (заданное количество элементов массива)
8. Каков общий объём памяти, который потребляет эта программа?
1. Сколько памяти использует каждый элемент массива?
Для ответа на этот вопрос (и на другие подобные вопросы) нужно разделить общую память, потребляемую программой, на длину массива. Здесь мы будем использовать, для указания длины массива, число, представленное переменной MAGIC_ARRAY_LENGTH , равное 1304209. Позже, в комментариях к одному из вопросов, мы остановимся на том, почему здесь используется именно это значение. Пока же отметим, что столь большая длина массива позволяет абстрагироваться от потребления памяти другими частями программ.
Итак, вот код, который вам предлагается проанализировать.
Варианты ответа
Для хранения одного элемента такого массива требуется 8 байт. Значение true сохраняется в массиве в виде ссылки на объект, так же, как это происходит со строками. В результаты нам снова требуется записывать в элементы массива 64-битные адреса. Значения false , undefined и null обрабатываются похожим образом.
Указатели и двумерные массивы
При размещении элементов двумерных массивов они располагаются в памяти подряд по строкам, т.е. быстрее всего изменяется последний индекс , а медленнее – первый. Такой порядок дает возможность обращаться к любому элементу двумерного массива, используя адрес его начального элемента и только одно индексное выражение .
где k – количество байтов, выделяемое для элемента массива (в зависимости от типа).
Указатели на двумерные массивы в языке С++ – это массивы массивов, т.е. такие массивы, элементами которых являются массивы. При объявлении таких массивов в памяти компьютера создается несколько различных объектов. Например, при выполнении объявления двумерного массива:
- В памяти выделяется участок для хранения значения переменной arr , которая является указателем на массив из четырех указателей.
- Для этого массива из четырех указателей тоже выделяется память. Каждый из этих четырех указателей содержит адрес одномерного массива из трех элементов типа int .
- Следовательно, в памяти компьютера выделяется четыре участка для хранения четырех массивов чисел типа int , каждый из которых состоит из трех элементов.
Схематично распределение памяти для данного двумерного массива выглядит так:
Таким образом, объявление arr[4][3] порождает в программе три разных объекта:
- указатель с идентификатором arr ,
- безымянный массив из четырех указателей: arr[0], arr[1], arr[2], arr[3]
- безымянный массив из двенадцати чисел типа int .
- Для доступа к безымянным массивам используются адресные выражения с указателем arr . Доступ к элементам одномерного массива указателей осуществляется с указанием одного индексного выражения в форме arr[2] или *(arr+2) .
- Для доступа к элементам двумерного массива чисел типа int arr[i][j] должны быть использованы следующие выражения:
Например, пусть i=1, j=2 , тогда обращение к элементу arr[1][2] :
- arr[i][j] arr[1][2]=10
- *(*(arr+i)+j) *(*(arr+1)+2)=10
- (*(arr+i))[j] (*(arr+1))[2]=10
Причем внешне похожее обращение arr[5] выполнить невозможно, так как указателя с индексом 5 не существует.
Пример 2 . Использование индексных и адресных выражения при обработке двумерных массивов.
Как известно, JavaScript-движок V8 весьма популярен. Он применяется в браузере Google Chrome, на нём основана платформа Node.js. В материале, подготовленном Мэттом Зейнертом, перевод которого мы публикуем сегодня, приведено девять вопросов, посвящённых особенностям того, как V8 работает с памятью. Каждый вопрос содержит фрагмент кода, который нужно проанализировать и найти ответ, наиболее точно описывающий потребление памяти этим кодом или представленными в нём структурами данных. Ответы на вопросы снабжены комментариями.
Разобравшись с этим материалом, вы узнаете о некоторых интересных особенностях того, как V8 обращается с памятью. Возможно, понимание этих особенностей не пригодится вам при поиске проблем с производительностью тех программ, которые вы пишете постоянно, но, полагаем, это поможет вам лучше понять внутренние механизмы движка V8, а значит, может сослужить хорошую службу при анализе какой-нибудь необычной ситуации.
О чём нам это говорит?
PHP не C. И это говорит нам только об этом. Вы не можете ожидать от супер-динамического языка PHP эффективного использования памяти как в C. Не можете и всё.
Но, если вы хотите сохранить память вы можете рассмотреть использование SplFixedArray для больших статических массивов.
Посмотрим на модифицированный скрипт:
В основном он делает то же самое, но, если вы его запустите, то вы заметите, что он использует «всего лишь» 5 600 640 байт. Что составляет 56 байт на элемент, а это намного меньше, чем 144 байта на элемент обычного массива. Это происходит потому, что фиксированный массив не нуждается в bucket структуре: так что требуется только один zval (48 байт) и один указатель (8 байт) для каждого элемента, что даст нам наблюдаемые 56 байт.
то сколько выделится памяти под массив? Зависит от компилятора?
Я предполагаю, что выделится ровно столько, сколько элементов перечислено в списке (т. е. sizeof(тип) * количество элементов в списке).
Совершенно верно за одним исключением. Памяти может выделятся чуть больше из-за выравнивания. Доступ к памяти по выровненным адресам намного быстрее происходит, из-за чего эффективнее например выделить дополнительный байт в начале (что бы адрес массива стал кратным 2/4/8/16 (смотря какое выравнивание в архитектуре используется).
dableproger: возможно попробовать перед массивом объявить переменную и вычислить разность адреса массива и адреса переменной и ещё отнять размер переменной. Получите размер неиспользуемого места перед массивом.
Вот только я не уверен, гарантирует ли компилятор определённый порядок переменных. В структурах гарантирует точно.
fshp: гарантирует. В обеих случаях. И на стеке и на куче.
Вот только в случае со стеком - они будут в обратном порядке. Стек растет в сторону уменьшения адресов, то есть более "поздняя" переменная выделится в стеке "выше", т.е по адресам она будет раньше.
Связано это с тем, что локальные переменные на стеке выделяются через вычитание требуемой памяти из указателя на вершину стека, и добавление инициализированных переменных через push до этого, которая кладет их "сверху" стека.
SolidMinus: на куче точно не гарантирует, т.к. память может быть фрагментирована. И если первое выделение не помещается в первый фрагмент, а второе помещается - то адреса будут в обратном порядке.
А со стеком верное замечание, да.
Пытаться гадать о внутреннем строении компилятора по адресам локальных переменных менее эффективно, чем делать то же на кофейной гуще.
MiiNiPaa: переменные на которые есть reference в коде удалить он не может, он может их оптимизировать в использование регистрами, в примере он их удалил потому что дабл нигде не используется, але)))
Но если в коде будет оператор & перед именем переменной - в регистр он ее не запихнет, т.к у регистров нет адреса в памяти.
Изменение порядка такое связано с тем, что double занимает памяти больше чем int, поэтому сначала пушится инт, потом дабл. Имелось ввиду переменные одного типа.
Ты лучше не адреса выводи через принтф, а дизассемблируй мейн функцию и смотри как что меняется там.
long unsigned int = 4 байта.
1024 * 1024 * long unsigned int = 4 мегабайта.
Почему же массив, наполненный числами от 0 до 1024 * 1024 занимает в памяти 40 мегабайт? (в php еще в два раза больше). Я понимаю, у каждого элемента есть там индекс, указатели какие-то, но хочу не понимать, а четко знать, куда моя память тратится?
По просьбе трудящихся прикладываю код:
По просьбе трудящихся прикладываю скрины:
Ubuntu (как это интерпретировать я хз, но кому интересно вот):
P.S. Из чего вообще возник этот вопрос - у меня есть около 3кк ip адресов, которым надо в соответствие поставить 3кк неких id. Но на это уходит почти гигабайт этой самой памяти (два массива по три миллиона чисел или объект с ключом-значением, разница небольшая), тогда как csv-файлик со двумя столбцами этих чисел весит всего 30мб.
Хотите четко знать - посмотрите исходники движков js и php. Организация памяти из внутреннее дело и может меняться от версии к версии конкретного продукта. Если даже сейчас это посмотреть и написать тут ответ, то завтра ситуация может в корне изменится
Указатели и двумерные массивы
При размещении элементов двумерных массивов они располагаются в памяти подряд по строкам, т.е. быстрее всего изменяется последний индекс , а медленнее – первый. Такой порядок дает возможность обращаться к любому элементу двумерного массива, используя адрес его начального элемента и только одно индексное выражение .
где k – количество байтов, выделяемое для элемента массива (в зависимости от типа).
Указатели на двумерные массивы в языке С++ – это массивы массивов, т.е. такие массивы, элементами которых являются массивы. При объявлении таких массивов в памяти компьютера создается несколько различных объектов. Например, при выполнении объявления двумерного массива:
- В памяти выделяется участок для хранения значения переменной arr , которая является указателем на массив из четырех указателей.
- Для этого массива из четырех указателей тоже выделяется память. Каждый из этих четырех указателей содержит адрес одномерного массива из трех элементов типа int .
- Следовательно, в памяти компьютера выделяется четыре участка для хранения четырех массивов чисел типа int , каждый из которых состоит из трех элементов.
Схематично распределение памяти для данного двумерного массива выглядит так:
Таким образом, объявление arr[4][3] порождает в программе три разных объекта:
- указатель с идентификатором arr ,
- безымянный массив из четырех указателей: arr[0], arr[1], arr[2], arr[3]
- безымянный массив из двенадцати чисел типа int .
- Для доступа к безымянным массивам используются адресные выражения с указателем arr . Доступ к элементам одномерного массива указателей осуществляется с указанием одного индексного выражения в форме arr[2] или *(arr+2) .
- Для доступа к элементам двумерного массива чисел типа int arr[i][j] должны быть использованы следующие выражения:
Например, пусть i=1, j=2 , тогда обращение к элементу arr[1][2] :
- arr[i][j] arr[1][2]=10
- *(*(arr+i)+j) *(*(arr+1)+2)=10
- (*(arr+i))[j] (*(arr+1))[2]=10
Причем внешне похожее обращение arr[5] выполнить невозможно, так как указателя с индексом 5 не существует.
Пример 2 . Использование индексных и адресных выражения при обработке двумерных массивов.
Как известно, JavaScript-движок V8 весьма популярен. Он применяется в браузере Google Chrome, на нём основана платформа Node.js. В материале, подготовленном Мэттом Зейнертом, перевод которого мы публикуем сегодня, приведено девять вопросов, посвящённых особенностям того, как V8 работает с памятью. Каждый вопрос содержит фрагмент кода, который нужно проанализировать и найти ответ, наиболее точно описывающий потребление памяти этим кодом или представленными в нём структурами данных. Ответы на вопросы снабжены комментариями.
Разобравшись с этим материалом, вы узнаете о некоторых интересных особенностях того, как V8 обращается с памятью. Возможно, понимание этих особенностей не пригодится вам при поиске проблем с производительностью тех программ, которые вы пишете постоянно, но, полагаем, это поможет вам лучше понять внутренние механизмы движка V8, а значит, может сослужить хорошую службу при анализе какой-нибудь необычной ситуации.
3. Сколько памяти использует каждый элемент массива?
Варианты ответа
- 1 байт
- 4 байта
- 8 байт
- 16 байт
- 24 байта
- 35 байт
Правильный ответ на этот вопрос — 8 байт. Дело тут в том, что числа в JavaScript представлены 64-битными значениями с плавающей запятой. В байте 8 бит, в результате каждое число занимает 64/8 = 8 байт.
Краткое изложение
Для тех, кто не хочет разбираться со всем этим, вот краткий обзор вовлечённых компонент.
Приведённые выше числа могут меняться в зависимости от вашей операционной системы, компилятора и опций компилирования. Например, если вы компилируете PHP с debug или thread-safety, то получите различные значения. Но я думаю, что приведённые размеры вы увидите на рядовой сборке PHP 5.3 на 64 разрядном Линуксе.
Если умножить эти 144 байта на наши 100 000 чисел, то получится 14 400 000 байт, что составляет 13,73 Мб. Довольно близко к реальному результату, остальное — это в основном указатели для неинициализированных блоков(buckets), но я расскажу об этом позже.
Теперь, если вы хотите иметь более детальный анализ значений, которые указаны выше, то читайте дальше :).
Варианты ответа
В данном случае массиву будет выделено 8 Мб памяти. Так как V8 заранее знает размер массива, система может выделить ему ровно столько памяти, сколько нужно.
Сборщик мусора для циклических ссылок (PHP 5.3)
PHP 5.3 представила новый сборщик мусора для циклических ссылок. Для этого PHP хранит некоторую дополнительную информацию. Я не хочу здесь объяснять как это работает, вы можете почерпнуть необходимую информацию из мануала. Для наших расчётов размеров важно, что каждый zval оборачивается zval_gc_info:
Как вы видите Zend только добавляет объединение, которое содержит два указателя. Как вы помните размер объединения определяется самым большим компонентом. Оба компонента — это указатели по 8 байт. Соответственно, размер объединения тоже 8 байт.
Если мы добавим полученные выше 24 байта, то мы получим 32 байта. Умножаем это на 100 000 и полуаем 3,05 Мб.
Структура zval
Вполне логично, что union хранит только значение, а PHP, очевидно, нужно хранить так же его тип и некоторую информацию для сборки мусора. Структура, которая содержит эту информацию, называется zval и вы, наверное, уже слышали о ней. Для получения дополнительной информации о том, зачем это PHP, я рекомендую прочитать статью Sara Golemon. Как бы то ни было эта структура определяется следующим образом:
Размер структуры определяется суммой размеров всех её компонент: zvalue_value — 16 байт (расчёт выше), zend_uint — 4 байта, zend_uchar — 1 байт каждый. В общей сложности 22 байта. Опять же из-за выравнивания памяти реальный размер будет 24 байта.
Так что, если мы храним 100 000 значений по 24 байта, то это будет 2 400 000 байт или 2,29 Мб. Разрыв сокращается, но реальное значение ещё более чем в шесть раз больше.
9. Каков общий объём памяти, который потребляет эта программа?
Варианты ответа
В данном случае правильным ответом будет 64 байта. Сколько памяти следует выделить движку V8 для хранения пустого объекта? Это — непростой вопрос. В частности, с учётом того, что предполагается, что объект не будет пустым всегда.
Вот список того, что будет хранить V8 для каждого пустого объекта:
- Ссылка на скрытый класс (8 байт).
- 4 пустых ячейки для хранения значений будущих свойств объекта (32 байта).
- Пустая ячейка для хранения ссылки на дополнительный объект, который будет использовать в том случае, если к исходному объекту будет добавлено более 4-х свойств (8 байт).
- Пустая ячейка для объекта, который хранит значения для индексов числовых свойств (8 байт).
В результате мы приходим к тому, что для одного элемента массива, состоящего из пустых объектов, понадобится 64 байта, в которые входят 56 байт, которые требуются для хранения объекта в памяти, и 8 байт, которые нужны для хранения ссылки на объект в массиве.
Варианты ответа
Правильный ответ — 8 байт. Каждый элемент массива должен хранить 64-битную ссылку на текстовое значение, хранящееся в памяти. V8 создаст лишь одну строку, хранящую текст «Hello», а все элементы массива будут ссылаться на неё. Поэтому, если учесть, что у нас имеется достаточно большой массив, размером строки можно пренебречь, и мы придём к тому, что для хранения одного элемента массива V8 понадобится 8 байт.
Варианты ответа
Эта программа потребляет 10 Мб памяти. Тут мы храним в массиве немного больше миллиона чисел, каждое из которых занимает 8 байт. В результате можно предположить, что массив займёт примерно 8 Мб памяти. Однако, на самом деле это не так. Элементы в JavaScript-массивы можно добавлять в любое время, но V8 не будет менять размер массива каждый раз, когда вы добавляете в него новый элемент. Для этого, выделяя память под массив, движок оставляет некоторый объём свободного пространства в конце массива.
В предыдущих примерах мы использовали число, представленное переменной MAGIC_ARRAY_LENGTH . Это число находится на границе «запасной» памяти, которая система выделяет массивам. Значение MAGIC_ARRAY_LENGTH равняется 1304209, в то время как 1024*1024 — это 1048576. Однако и в том и в другом случае объём памяти, используемый массивом, будет одним и тем же.
Варианты ответа
В данном случае правильный ответ — 24 байта. В этом примере мы ставим JS-движок в сложное положение. Дело в том, что массив содержит 2 разных типа данных — числа и строки.
В массиве хранится ссылка на строку. Ссылка — это просто указание на то, в какой области памяти хранятся данные (в нашем случае — символы, из которых состоит строка). Адрес в памяти — это число. Системную память можно воспринимать как огромный массив, а адреса в памяти можно считать индексами этого массива.
В результате оказывается, что ссылка на строку — это число, остальные элементы массива — тоже числа. Как их различить? Чем число-ссылка отличается от обычного числа?
Ответ на этот вопрос даёт термин «упакованное значение» («boxed value»). Система упаковывает каждое число в объект и хранит в массиве ссылку на этот объект. Теперь каждый элемент массива может быть ссылкой.
Для того, чтобы сохранить в массиве число, нам нужно поместить в память следующие данные:
- Ссылку на объект (8 байт)
- Сам объект, в который упаковано число (16 байт)
Почему для хранения ссылки нужно 8 байт? Помните о том, что системная память похожа на массив? Если используется 32-битная система адресации, то с её помощью можно выразить индексы массива вплоть до 2^32. Если вы храните один байт по каждому индексу массива, это значит, что вы можете оперировать 2^32/(1024*1024*1024) = 4 Гб памяти. Так как большинство компьютеров в наши дни имеют больше чем 4 Гб памяти, для работы с ней приходится использовать 64-битные адреса (для хранения адреса требуется 8 байт). Это — довольно упрощённое пояснение происходящего, однако, оно даёт представление о том, как работает система адресации.
Блоки
Как вы видите необходимо хранить «груз» данных, чтобы получить абстрактный массив данных вроде такого, какой используется в PHP (массивы PHP являются массивами, словарями и связными списками в одно и тоже время, что, конечно, требует много данных). Размер отдельных компонент это: 8 байт для типа ulong, 4 байта для uint и 7 раз по 8 байт для указателей. В результате получается 68. Добавляем выравнивание и получаем 72 байта.
Для блоков как и для zval должны быть добавлены заголовки в 16 байт, что даёт нам 88 байт. Так же нам нужно хранить указатели на эти блоки в «настоящем» массиве C (Bucket **arBuckets;), я упомнил об этом выше, что добавляет ещё 8 байт на элемент. Так что в целом каждый блок расходует в 96 байтах памяти.
И так, если нам нужен блок для каждого значения — это будет 96 байт для bucket и 48 байт для zval, что составляет 144 байта в общей сложности. Для 100 000 элементов это будет 14 400 000 байт или 13,73 Мб.
7. Каков общий объём памяти, который потребляет эта программа?
Варианты ответа
Этой программе понадобится 2 Мб памяти, так как массив содержит лишь 16-битные целые числа, каждое из которых занимает 2 байта. Таких чисел немного больше миллиона, что означает необходимость в 2-х мегабайтах памяти.
Итоги
Если вы хотите самостоятельно поэкспериментировать со всем тем, чему посвящены вопросы из этого материала, применяя инструменты разработчика Chrome, вы можете воспользоваться следующей конструкцией:
Здесь интересующее нас значение помещено в класс Holder , что упрощает его поиск.
Исследование памяти с помощью инструментов разработчика Chrome
Кстати, автор этого материала говорит, что, проводя эксперименты, он пока не смог до конца понять, как V8 работает в памяти со строками. Если вам удастся это выяснить — уверены, многим будет интересно об этом узнать.
В этой статье я хочу исследовать расход памяти у массивов (и значений в целом) в PHP используя следующий скрипт в качестве примера, который создаёт 100 000 уникальных целочисленных элементов массива и в конце измеряет количество использованной памяти.
Это перевод (для таких как я, которые этого часто не замечают).
В начале я хочу поблагодарить Johannes и Tyrael за их помощь в поисках укромных мест расхода памяти.
Как вы думаете сколько получится? Если целое число это 8 байт (на 64 архитектурах и используя тип long) и есть 100 000 целых чисел, то, очевидно, потребуется 800 000 байт. Это около 0,76 Мб.
Теперь попробуйте запустить код. Это можно сделать on-line. В результате получится 14 649 024 байт. Да, вы не ослышались, это 13,97 Мб — в 18 раз больше, чем мы прикинули.
Итак, откуда появилось это 18 кратное увеличение?
2. Сколько памяти использует каждый элемент массива?
Объединение zvalue_value
Если вы не знаете C, то это не проблема — код очень прост: объединение означает, что значение может выступать в роли различных типов. Например, если вы используете zvalue_value->lval, то значение будет интерпретировано как целое число. С другой стороны, если используете zvalue_value->ht, то значение будет интерпретировано как указатель на хеш-таблицу (aka массив).
Не будем на этом задерживаться. Важным для нас только то, что размер объединения равен размеру его крупнейшего компонента. Самый большой компонент — это строка (на самом деле структура zend_object_value имеет тоже размер, но этот момент я опущу для простоты). Структура состоит из указателя (8 байт) и целого числа (4 байта). Итого 12 байт. Благодаря выравниванию памяти (структуры в 12 байт — это не круто, потому что они не являются произведением 64 бит/8 байт) конечный размер структуры будет 16 байт и, соответственно, всего объединения в целом.
Итак, теперь мы знаем, что нам нужно не 8 байт для каждого значения, а 16 — за счёт динамической типизации PHP. Умножив на 100 000 получим 1 600 000 байт, т.е. 1,53 Мб. Но реальный объём 13,97 Мб, поэтому мы не достигли пока цели.
4. Сколько памяти использует каждый элемент массива?
Подождите, осталось ещё 0,24 Мб!
Эти последние 0,24 Мб обусловлены неинициализированными блоками: размер «реального» массива C в идеале должен быть равен количеству элементов. Таким образом мы получаем наименьшее количество коллизий (если вы не хотите тратить много памяти). Но PHP, очевидно, не может перераспределять весь массив каждый раз когда добавляется новый элемент — это было бы ооочень медленно. Вместо этого PHP всегда удваивает размер внутреннего массива блоков, если оно попадает в предел. Таким образом, размер массива всегда является степенью двойки.
В нашем случае это 2 ^ 17 = 131 072. Но нам нужно только 100 000 из этих блоков, поэтому мы оставляем 31 072 блока неиспользованными. Те, память под эти блоки выделена не будет (поэтому нам не надо тратить полные 96 байт), но память под указатель(который хранится в внутреннем массиве блоков) на блок должна быть использована. Поэтому мы дополнительно используем 8 байт (на указатель) * 31 072 элементов. Это 248 576 байт или 0,23 Мб. Что соответствует недостающей памяти. (Конечно, отсутствуют ещё несколько байт, но я не хочу полностью покрыть всё. Это такие вещи как сама структура хэш-таблицы, переменные и т.д.)
Загадка действительно решена.
6. Сколько памяти использует каждый элемент массива?
1 ответ 1
Для начала, не стоит в языках со сборкой мусора пытаться что-либо связанное с внутренним устройством памяти замерить снаружи. RSS - объем ОП, который занимает сам процесс ноды, с тем, сколько реально "занято", а сколько "прозапас" коррелирует слабо.
В частности, нода, очень любит кушать память. Пока система дает ей оперативную память, она будет ее запрашивать, если происходит много аллокаций. Сделано это прежде всего для скорости работы.
Как верно заметил Artur Udod в комментарии, в javascript, согласно стандарту, все числа - 64х битовые, т.е. 8 байт на число.
Чтобы замерить правильно размер 1 элемента массива - необходимо снимать хипдамп, искать там нужный объект и смотреть, а сколько же реально места там занято.
Есть вариант грубого замера. Для этого - запускаем node --expose-gc . Данный флаг позволяет принудительно инициировать отчистку мусора.
Посредством нехитрых вычислений вида:
Получим примерный размер элемента массива в памяти. Тут следует учитывать, что будет погрешность в большую сторону, т.к. REPL на разбор запросов тоже будет кушать память. Причем из той же кучи.
Замерил хип дампом, через аллокацию массива с 100к элементов:
Здесь - 8,53 байта на элемент массива. Однако, стоит помнить, что сам массив тоже занимает память.
В целом, Mike верно подметил, внутреннее устройство, действительно сильно зависит от версии движка, с этим ничего не поделаешь.
Еще один забавный момент заключается в том, что внутреннее устройство массива в V8 сильно зависит от его размера и структуры. К примеру в node версии 5.7.1 массив на 810 312 элементов "весит" значительно больше (примерно 12 байт на элемент), чем на 810 311 элементов (8,004 байта), это уже привет внутреннему устройству движка, вполне возможно, что он просто превращает массив в словарь, тут уже гадать не буду, однако с дальнейшим ростом, "размер" элемента массива опять падает.
Менеджер памяти ZEND
Си, в отличие от PHP, не управляет памятью за вас. Вы должны самостоятельно следить за распределением памяти. Для этого PHP использует оптимизированный для своих нужд собственный менеджер памяти: The Zend Memory Manager. MM Zend основан на malloc от Doug Lea и всяческих дополнительных специфических для PHP особенностей и оптимизаций (таких как ограничение памяти, очистка после каждого запроса и тому подобное).
Что важного для нас в этом так это то, что MM добавляет заголовок для каждого выделения памяти, которое проходит через него. И определяется следующим образом:
Для примера мы будем считать, что все эти опции отключены. В этом случае остается только две компоненты size_t _size и _prev. size_t занимет 8 байт (64 бита), так что заголовок имеет размер в 16 байт — и этот заголовок добавляется для каждого выделения памяти.
Так что мы должны скорректировать размер zval снова. На самом деле это будет не 32 байта, а 48, из-за этого заголовка. Умножаем на наши 100 000 элементов и получаем 4,58 Мб. Реальный размер 13,97 Мб, так что мы уже покрыли примерно треть.
Варианты ответа
Правильным ответом на этот вопрос будет 32 байта. Тут, опять же, в массиве хранятся пустые объекты, но в этот раз мы, для создания объектов, используем функцию-конструктор. V8 может изучить код программы, и понять, что объекты, создаваемые функцией Obj() , ничего не содержат.
- Ссылка на скрытый класс.
- Ячейка для хранения ссылки на объект с дополнительными свойствами.
- Ячейка для ссылки на объект, используемый для хранения индексов числовых свойств.
5. Сколько памяти использует каждый элемент массива?
Читайте также: