Объясните мне пожалуйста одну простую вещь.
long unsigned int = 4 байта.
1024 * 1024 * long unsigned int = 4 мегабайта.
Почему же массив, наполненный числами от 0 до 1024 * 1024 занимает в памяти 40 мегабайт? (в php еще в два раза больше). Я понимаю, у каждого элемента есть там индекс, указатели какие-то, но хочу не понимать, а четко знать, куда моя память тратится?
По просьбе трудящихся прикладываю код:
var i = 1024 * 1024, arr = [];
while(i--) arr.push(i);
По просьбе трудящихся прикладываю скрины:
Win 7:
Win 8:
Ubuntu (как это интерпретировать я хз, но кому интересно вот):
P.S.
Из чего вообще возник этот вопрос - у меня есть около 3кк ip адресов, которым надо в соответствие поставить 3кк неких id. Но на это уходит почти гигабайт этой самой памяти (два массива по три миллиона чисел или объект с ключом-значением, разница небольшая), тогда как csv-файлик со двумя столбцами этих чисел весит всего 30мб.
Ответ
Для начала, не стоит в языках со сборкой мусора пытаться что-либо связанное с внутренним устройством памяти замерить снаружи. RSS - объем ОП, который занимает сам процесс ноды, с тем, сколько реально "занято", а сколько "прозапас" коррелирует слабо.
В частности, нода, очень любит кушать память. Пока система дает ей оперативную память, она будет ее запрашивать, если происходит много аллокаций. Сделано это прежде всего для скорости работы.
Как верно заметил Artur Udod в комментарии, в javascript, согласно стандарту, все числа - 64х битовые, т.е. 8 байт на число.
Чтобы замерить правильно размер 1 элемента массива - необходимо снимать хипдамп, искать там нужный объект и смотреть, а сколько же реально места там занято.
Есть вариант грубого замера. Для этого - запускаем node --expose-gc. Данный флаг позволяет принудительно инициировать отчистку мусора.
Посредством нехитрых вычислений вида:
var a = [];
var memBefore = 0;
var memAfter = 0;
var length = 1000000;
gc();
memBefore = process.memoryUsage().heapUsed;
(function(len){
for(var i = 0; i < len; i++){
a[i] = i;
}
})(length);
gc();
memAfter = process.memoryUsage().heapUsed;
console.log('Memory used by 1 element is:', (memAfter - memBefore) / length);
Получим примерный размер элемента массива в памяти. Тут следует учитывать, что будет погрешность в большую сторону, т.к. REPL на разбор запросов тоже будет кушать память. Причем из той же кучи.
Замерил хип дампом, через аллокацию массива с 100к элементов:
Здесь - 8,53 байта на элемент массива. Однако, стоит помнить, что сам массив тоже занимает память.
В целом, Mike верно подметил, внутреннее устройство, действительно сильно зависит от версии движка, с этим ничего не поделаешь.
Еще один забавный момент заключается в том, что внутреннее устройство массива в V8 сильно зависит от его размера и структуры. К примеру в node версии 5.7.1 массив на 810 312 элементов "весит" значительно больше (примерно 12 байт на элемент), чем на 810 311 элементов (8,004 байта), это уже привет внутреннему устройству движка, вполне возможно, что он просто превращает массив в словарь, тут уже гадать не буду, однако с дальнейшим ростом, "размер" элемента массива опять падает.
Комментариев нет:
Отправить комментарий