Привет, Хабаровчане! Во второй статье, хочу поделиться наблюдениями из документации V8 и немного нудной информацией для многих 🙂
Что есть Объект?
Казалось бы, объект в JS — это просто набор ключ-значений. Но для движка V8 это структура с жёсткой схемой: каждый объект имеет Hidden Class ( или Map), который описывает:
-
Какие свойства есть у объекта;
-
Их порядок;
-
Смещения в памяти для быстрого доступа.
Если структура стабильна, JIT компилятор может сделать доступ к свойствам быстрее, чем если бы структура была хаотична.
Как же формируются Map и почему важен порядок?
Допустим у нас есть следующий код:
function createUser() { const user = {}; user.name = "Victor"; user.age = 30; return user; } const obj1 = createUser();
Внутри V8 создаётся цепочка Map transitions:
Все объекты, созданные этой функцией, будут иметь одинаковый Map.
А теперь изменим порядок:
function createUser() { const user = {}; user.age = 30; // Поместили свойство age выше user.name = "Victor"; return user; } const obj2 = createUser();
Теперь цепочка выглядит так:
Теперь obj1 и obj2 — это разные структуры для V8, что ломает оптимизацию.
Можно протестировать через d8. d8 — shell, позволяющий работать с V8 как с интерпретатором или компилятором JavaScript. Про него написано в документации. Проходим круги ада, собрав его и запускаем для теста нашего кода:
function User1() { this.name = "Victor"; this.age = 30; } function User2() { this.age = 30; this.name = "Victor"; } const obj1 = new User1(); const obj2 = new User2(); %DebugPrint(obj1); %DebugPrint(obj2);
В консоли мы наблюдаем следующее:
DebugPrint: 0x7f8a9c1234: [JS_OBJECT_TYPE] - map: 0x5e3b7d84f2 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x7f8a9a77bc - properties: 0x3d4f8a1b92 - elements: 0x6b7d2c9e18 - name: "Victor" - age: 30 DebugPrint: 0x7f8a9c5678: [JS_OBJECT_TYPE] - map: 0x4ad2f38c91 <Map(HOLEY_ELEMENTS)> [FastProperties] - prototype: 0x7f8a9a77bc - properties: 0x5a6c1e8b77 - elements: 0x1c9e2a3f54 - age: 30 - name: "Victor"
Как мы видим map у объектов разные (0x5e3b7d84f2 и 0x4ad2f38c91) и порядок свойств отличается. Это и есть разные Hidden Classes (Map)
Почему это так важно?
V8 использует JIT и Inline Caching. Если функция видит только один Map — то мономофризм IC (когда функция видит только один тип объекта). Если 2–3 — полиморфизм IC ( когда функция встречает два-три разных типа объектов и доступ уже чуть медленнее). Если выше — мега-морфизм IC (когда функция сталкивается со множеством разных типов объектов).
Вот еще Бенчмарк кода:
function makeObjOrdered() { return { a: 1, b: 2, c: 3 }; } function makeObjRandom() { const o = {}; if (Math.random() > 0.5) { o.a = 1; o.b = 2; o.c = 3; } else { o.c = 3; o.a = 1; o.b = 2; } return o; } console.time("ordered"); for (let i = 0; i < 1e7; i++) { const o = makeObjOrdered(); void o.a; } console.timeEnd("ordered"); console.time("random"); for (let i = 0; i < 1e7; i++) { const o = makeObjRandom(); void o.a; } console.timeEnd("random");
Его можно запустить с помощью Node.js и мы увидим следующее:
ordered: 5.959ms random: 156.761ms
Неплохая такая разница, правда? Это всё из за мега-морфного доступа IC.
UPD
Как заметили в комментариях коллеги по цеху, моё упущение было, не сказав и не показав тесты на более равных условиях. Поэтому исправляюсь.
При строгом контроле структур объектов и минимальной вариативности разница по времени небольшая (10–15%). Но даже такая разница показывает, что разные Map влияют на производительность. На практиrе с более хаотичными структурами и большим разбросом разница будет гораздо сильнее (как в исходном примере).
function makeObjOrdered() { return (Math.random() > 0.5) ? {a: 1, b: 2, c: 3} : {a: 1, b: 2, c: 3} } function makeObjRandom() { return (Math.random() > 0.5) ? {a: 1, b: 2, c: 3} : {o: 3, a: 1, b: 2} } const arr1 = [] console.time("ordered") for (let i = 0; i < 1e7; i++) { const o = makeObjOrdered() arr1.push(o.a) } console.timeEnd("ordered") console.log(arr1) const arr2 = [] console.time("random") for (let i = 0; i < 1e7; i++) { const o = makeObjRandom() arr2.push(o.a) } console.timeEnd("random") console.log(arr2)
Вывод в консоль:
ordered: 202.108ms random: 229.607ms
Заключение
Надеюсь вы дочитали до конца эту небольшую и нудную статью. Порядок свойств в объекте — это важный фактор оптимизации в V8. Если структура стабильна, движок использует быстрый monomorphic IC, если нет — то мега-морфный режим. Так что не поскупитесь и нормализуйте данные на бекенде, используйте мидлвары для приведения данных в единую структуру на фронтенде (дабы обезопасить себя), использовать TypeScript,ну и кэшировать объекты, чтобы не создавать новых с разными Maps без необходимости. Буду рад отзывам и комментариям 🙂
ссылка на оригинал статьи https://habr.com/ru/articles/935786/
Добавить комментарий