Загрузка...
Загрузка...
Продолжая работу с платформой, вы принимаете условия Политики конфиденциальности и использование файлов cookie.
V8 — это высокопроизводительный JavaScript движок от Google, используемый в Chrome и Node.js. Понимание его архитектуры критически важно для написания оптимизированного кода.
function add(a, b) {
return a + b;
}
AST (Abstract Syntax Tree):
{
"type": "FunctionDeclaration",
"id": { "name": "add" },
"params": [
{ "name": "a" },
{ "name": "b" }
],
"body": {
"type": "ReturnStatement",
"argument": {
"type": "BinaryExpression",
"operator": "+",
"left": { "name": "a" },
"right": { "name": "b" }
}
}
}
Ignition преобразует AST в bytecode и начинает выполнение:
// Bytecode для add(a, b)
Ldar a0 // Load argument 0 (a) в аккумулятор
Add a1, [0] // Добавить argument 1 (b)
Return // Вернуть результат
Зачем bytecode?
Sparkplug (добавлен в V8 9.1) компилирует часто вызываемый bytecode в неоптимизированный машинный код:
Bytecode → Machine Code (1:1 mapping)
Преимущества:
TurboFan создаёт высокооптимизированный машинный код на основе feedback:
function add(a, b) {
return a + b;
}
// После 1000+ вызовов с числами
add(1, 2); // V8 замечает: всегда числа!
add(5, 10);
add(100, 200);
// TurboFan оптимизирует: предполагая числа
// Генерирует машинный код для чисел напрямую
Оптимизации TurboFan:
function calculate(x) {
return x * 2;
}
// Вызов 1-100: Ignition (bytecode)
for (let i = 0; i < 100; i++) calculate(i);
// Вызов 100+: Sparkplug (baseline)
// V8: "Эта функция горячая, скомпилируем в baseline"
// Вызов 1000+: TurboFan (optimized)
// V8: "Всегда числа! Оптимизируем под числа"
function calculate(x) {
return x * 2;
}
// V8 оптимизировал под числа
for (let i = 0; i < 10000; i++) {
calculate(i); // Числа - оптимизированный код
}
// Неожиданный тип!
calculate("hello"); // Строка - DEOPT!
// V8 откатывается к bytecode и заново собирает feedback
Стоимость deopt:
V8 оптимизирует доступ к свойствам объектов через Hidden Classes:
class Point {
constructor(x, y) {
this.x = x; // Hidden Class C0 → C1
this.y = y; // Hidden Class C1 → C2
}
}
const p1 = new Point(1, 2);
const p2 = new Point(3, 4);
// p1 и p2 имеют одинаковый Hidden Class C2
Hidden Class хранит:
// Плохо: разные Hidden Classes
const p1 = { x: 1, y: 2 };
const p2 = { y: 2, x: 1 }; // Другой порядок!
// Плохо: dynamic properties
const p3 = { x: 1, y: 2 };
p3.z = 3; // Новый Hidden Class!
// Хорошо: одинаковая структура
const p4 = { x: 1, y: 2 };
const p5 = { x: 3, y: 4 }; // Тот же Hidden Class
Inline Cache ускоряет доступ к свойствам, кешируя их location:
function getX(point) {
return point.x;
}
// Первый вызов
getX({ x: 1, y: 2 });
// V8: "x находится по offset 0 для Hidden Class C2"
// Последующие вызовы
getX({ x: 3, y: 4 });
// V8: "Тот же Hidden Class! Используем кеш - offset 0"
Типы IC:
// Всегда один Hidden Class
getX({ x: 1, y: 2 });
getX({ x: 3, y: 4 });
getX({ x: 1, y: 2 });
getX({ x: 1, y: 2, z: 3 }); // Другой Hidden Class
// Плохо: слишком много разных структур
for (let i = 0; i < 10; i++) {
getX({ x: 1, [i]: i }); // Каждый раз новый Hidden Class!
}
Идея: Большинство объектов умирают молодыми.
Scavenge GC (Young Generation):
// Создаём временные объекты
function process() {
const temp = { data: new Array(1000) }; // Умрёт сразу
return temp.data.length;
}
// temp уничтожается Scavenge GC (~1-2ms)
Mark-Sweep-Compact (Old Generation):
// Долгоживущие объекты
const cache = new Map(); // Выживает Scavenge → Old Gen
cache.set('key', largeData);
// Удаляется Major GC (~50-100ms)
Не меняйте типы аргументов функций. Используйте TypeScript для контроля типов.
Инициализируйте все свойства в конструкторе. Не добавляйте свойства динамически.
Функции должны работать с объектами одинаковой структуры (Hidden Class).
delete obj.prop создаёт новый Hidden Class. Используйте obj.prop = undefined.
// Плохо: разные типы
function add(a, b) {
return a + b;
}
add(1, 2); // Числа
add("a", "b"); // Строки - DEOPT!
// Хорошо: один тип
function addNumbers(a, b) {
return a + b;
}
function addStrings(a, b) {
return a + b;
}
// Плохо: dynamic properties
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
const p = new Point(1, 2);
p.z = 3; // Новый Hidden Class!
// Хорошо: все свойства в конструкторе
class Point3D {
constructor(x, y, z = 0) {
this.x = x;
this.y = y;
this.z = z; // Всегда одинаковая структура
}
}
// Проверка оптимизации функции
%OptimizeFunctionOnNextCall(myFunction); // --allow-natives-syntax
myFunction();
%GetOptimizationStatus(myFunction);
# Запуск с флагами V8
node --trace-opt --trace-deopt app.js
# Вывод:
# [optimizing: myFunction / 0x...]
# [bailout: myFunction - type mismatch]
Итог:
V8 использует многоуровневую компиляцию (Ignition → Sparkplug → TurboFan) для баланса между скоростью старта и производительностью. Понимание Hidden Classes, Inline Caching и условий деоптимизации помогает писать код, который V8 может эффективно оптимизировать.