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 МБ. Как вы можете видеть: кардинальные изменения; -)
Таким образом, чтобы определить требования к памяти вашего фактического, законченного приложения и любые альтернативы реализации для него, вам придется измерить вещи самостоятельно.