Javascript сравнить размер объекта в памяти - PullRequest
0 голосов
/ 30 октября 2019

У меня около миллиона строк в javascript, и мне нужно хранить объект для метаданных для каждой из строк. Имеются следующие два разных типа объектов:

{0: {'e', 0, 'v': 'This is a value'}

И:

{0: '0This is a value'}

Какая будет разница в памяти между миллионами объектов первого типа имиллион объектов второго типа? То есть:

[obj1, obj1, obj1, ...] // array of 1M
[obj2, obj2, obj2, ...] // array of 1M

1 Ответ

2 голосов
/ 30 октября 2019

V8 разработчик здесь. Ответ по-прежнему "это зависит", потому что движки для динамического языка, как правило, адаптируются к тому, что вы делаете, поэтому крошечный тестовый сценарий, скорее всего, не отражает поведение реального приложения. Одно высокоуровневое эмпирическое правило, которое всегда будет выполняться: одна строка занимает меньше памяти, чем объект, обертывающий эту строку. Насколько меньше? Зависит.

Тем не менее, я могу дать конкретный ответ для вашего конкретного примера. Для следующего кода:

const kCount = 1000000;
let a = new Array(kCount);
for (let i = 0; i < kCount; i++) {
  // Version 1 (comment out the one or the other):
  a[i] = {0: {'e': 0, 'v': 'This is a value'}};
  // Version 2:
  a[i] = {0: '0This is a value'};
}
gc();

работает с --expose-gc --trace-gc, я вижу:

Версия 1: 244,5 МБ

Версия 2: 206,4 МБ

(Почти текущая оболочка V8, x64, d8. Это то, что @ paulsm4 предложил вам сделать в DevTools самостоятельно.)

Разбивка выглядит следующим образом:

  • самому массиву потребуется 8 байтов на запись
  • объект, созданный из литерала объекта, имеет заголовок из 3 указателей и предварительно выделенное пространство для 4 именованных свойств (здесь не используется), всего 7 * 8 = 56 байт
  • его резервное хранилище для индексированных свойств выделяет пространство для 17 записей, даже если будет использоваться только одна, плюс заголовок с 19 указателями = 152 байта
  • в версии 1 у нас есть внутренний объект, который обнаруживает, что два (итребуется только два) именованных свойства, поэтому оно усекается до размера 5 (3 заголовка, 2 для "e" и "v") указателей = 40 байт
  • в версии 2 нет внутреннего объекта, простоуказатель на строку
  • строковые литералы arДедуплицируется, и 0 сохраняется как «Smi» непосредственно в указателе, поэтому ни одному из них не требуется дополнительное пространство.

Суммирование:

Версия 1: 8 + 56 +152 + 40 = 256 байт на объект

Версия 2: 8 + 56 + 152 = 216 байт на объект

Однако все изменится резко , если не все строки будутто же самое, если объекты имеют больше или меньше именованных или индексированных свойств, если они исходят от конструкторов, а не литералов, если они растут или уменьшаются в течение своей жизни, а также из множества других факторов. Честно говоря, я не думаю, что какое-либо особенно полезное понимание можно почерпнуть из этих цифр (в частности, хотя они могут показаться довольно неэффективными, на практике они вряд ли произойдут таким образом - держу пари, что вы на самом деле не храните так многонулями, и упаковка фактических данных в объект с одним свойством {0: ...} тоже не выглядит реалистично).

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

for (let i = 0; i < kCount; i++) {
  a[i] = i.toString();
}

, который потребляет всего ~ 31 МБ. Предпочитаете фактический объект для метаданных?

function Metadata(e, v) {
  this.e = e;
  this.v = v;
}
for (let i = 0; i < kCount; i++) {
  a[i] = new Metadata(i, i.toString());
}

Теперь у нас ~ 69 МБ. Как вы можете видеть: кардинальные изменения; -)

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

...