Суть Читайте реальные JS-сниппеты и строку deopt-трассы, предсказывайте поведение V8 — поломку hidden class, дрейф monomorphic→megamorphic, триггеры deopt — и выбирайте самый эффективный фикс.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Баги производительности V8 прячутся в обычном на вид коде. Прочитайте каждый сниппет и строку трассы, предскажите, что V8 делает с hidden class, IC или оптимизированным уровнем — затем выберите фикс, который сеньор делает первым, до того как трогать флаг.
Цель
Отработайте цикл, который вы запускаете в каждом инциденте V8: прочитать горячий путь, предсказать, откуда берётся нестабильность формы или типа, и взять фикс выше по потоку, который держит сайт вызова monomorphic, а значения — стабильными по типу.
Сниппет 1 — позднее свойство
class Point { constructor(x, y) { this.x = x; this.y = y; }}function build(coords) { const out = []; for (const c of coords) { const p = new Point(c.x, c.y); if (c.label) p.label = c.label; // только иногда out.push(p); } return out;}// позже, в горячем цикле:function sumX(points) { let s = 0; for (const p of points) s += p.x; // горячий IC-сайт return s;}
Викторина
Completed
Некоторым точкам .label добавляется после конструктора, некоторым — нет. Что происходит на сайте доступа p.x в sumX и каков фикс?
Heads-up IC привязан ко всему hidden class, а не к читаемому свойству. Добавление .label переводит объект в другой класс, так что сайт p.x теперь видит два класса и становится polymorphic.
Heads-up В игре только два hidden class (с label и без), так что сайт polymorphic, а не megamorphic. Megamorphic требует 5+ классов — но фикс тот же: свести форму к одной.
Heads-up В V8 нет GOGC, и GC тут ни при чём — проблема в расхождении hidden class на IC-сайте, чинится объявлением label заранее.
readPort находится на горячем пути запроса. Что делает с ним delete в makeConfig и каков фикс?
Heads-up Крошечная экономия памяти ничтожна по сравнению с ценой: delete навсегда выводит объект из режима fast-property, так что любое последующее чтение любого свойства идёт по медленному общему пути.
Heads-up Объекты в режиме fast-property читаются по предвычисленному смещению независимо от числа свойств; сканирования нет. delete заменяет это хешмап-поиском, который строго медленнее.
Heads-up Цикл присваивания по ключам может перекручивать hidden class, но delete — решающий дефект: он опрокидывает cfg в dictionary mode, из которого V8 не восстановится без нового объекта.
Сниппет 3 — переполнение Smi
function checksum(ids) { let acc = 0; for (let i = 0; i < ids.length; i++) { acc = (acc * 31 + ids[i]); // быстро растёт на больших массивах } return acc;}
Викторина
Completed
checksum оптимизирован TurboFan и быстр на малых входах, но на больших массивах внезапно делает deopt и замедляется. В чём причина на уровне V8 и каков фикс?
Heads-up Индексированный цикл в пределах границ — именно тот паттерн, который TurboFan хорошо оптимизирует. Причина deopt здесь — переход acc из Smi в HeapNumber, а не проверка границ.
Heads-up Целочисленное умножение остаётся Smi, пока результат влезает в ±2³¹; V8 его оптимизирует. Deopt случается только когда acc выходит из диапазона и боксируется в HeapNumber.
Heads-up Замедление локализуется в этой функции после события deopt, а не глобальная пауза. Триггер — переход числового типа acc, который предотвращает приведение | 0.
Сниппет 4 — трасса deopt
[marking 0x... <JSFunction processItem> for optimization using TurboFan][deoptimizing (DEOPT eager): begin <JSFunction processItem> (opt #42) @14, ;;; deoptimize at <main.js:42:18>, wrong map][marking 0x... <JSFunction processItem> for optimization using TurboFan][deoptimizing (DEOPT eager): begin <JSFunction processItem> (opt #43) @14, ;;; deoptimize at <main.js:42:18>, wrong map][marking 0x... <JSFunction processItem> for optimization using TurboFan][deoptimizing (DEOPT eager): begin <JSFunction processItem> (opt #44) @14, ;;; deoptimize at <main.js:42:18>, wrong map]
Викторина
Completed
Читая этот вывод --trace-deopt, какое утверждение верно?
Heads-up 'map' — это внутреннее имя V8 для hidden class, а не отладочный source map. 'wrong map' значит, что hidden class объекта не совпал с тем, на который спекулировал TurboFan.
Heads-up Повторная разметка сразу после каждого deopt на той же строке — сигнатура death-spiral, а не здоровья. Стабильная оптимизированная функция размечается один раз и остаётся оптимизированной.
Heads-up Эти три — последовательные циклы opt #42/#43/#44 на одной строке — цикл, а не прогрев. Deopt при прогреве прекращаются, когда фидбэк стабилизируется; здесь он никогда не стабилизируется, потому что форма продолжает меняться.
Итог
Любой инцидент V8 читается одинаково в коде и трассах: условно добавленное свойство делит один массив на два hidden class и толкает горячий сайт в polymorphic; delete опрокидывает объект в постоянный dictionary mode; аккумулятор, переросший диапазон Smi, боксируется в HeapNumber и делает deopt цикла TurboFan; а повторяющийся ‘wrong map’ deopt на одной позиции — сигнатура deopt-loop. Диагностируйте по трассе, затем чините выше по потоку в модели данных — сведите форму к одной, уберите delete, ограничьте числовой диапазон — прежде чем браться за флаг.