Браузер и фронтенд-рантайм
Внутренности V8: охота на deopt и megamorphic IC
Читать про megamorphic IC и deopt-loop — не то же самое, что вытащить горячий путь из них. Постройте небольшую нагрузку, тяжёлую по выделению и вычислениям, намеренно загоните её в проблемы V8, затем через d8 и —trace-deopt диагностируйте и почините — с доказательством на каждом шаге.
Превратите ментальную модель юнита в воспроизводимый цикл: инструментируйте уровень и состояние IC, воспроизведите megamorphic-сайт и deopt-loop, почините форму и числовой тип выше по потоку и подтвердите восстановление измерениями до/после.
Возьмите намеренно враждебный к V8 горячий путь (свой или стартовый ниже) и верните его горячий сайт к monomorphic IC и стабильному уровню TurboFan без deopt-loop — доказывая каждый шаг выводом d8 с natives-syntax и --trace-deopt, а не догадками.
- Таблица до/после: состояние слота IC (mega → mono), уровень оптимизации (%GetOptimizationStatus), число deopt из --trace-deopt и пропускная способность горячего цикла — всё измерено, а не оценено.
- %DebugPrintFeedback ясно показывает, что целевой слот перешёл из MegamorphicSentinel к одному hidden class после фикса.
- --trace-deopt не выдаёт ни одного deopt для горячей функции на репрезентативном входе после фикса числового типа; сигнатура deopt-loop исчезла.
- Абзац-описание, называющий для каждой проблемы использованный рычаг (форма из конструктора, отказ от delete, заполнение null, | 0 или TypedArray) и почему он победил настройку любого флага.
- Добавьте вариант горячего цикла на параллельных TypedArray (Float32Array на поле) и сравните его пропускную способность и покадровый GC с версией на фиксированных объектах — оцените выигрыш от обхода IC и нулевого выделения.
- Добавьте perf-budget тест, прогоняющий горячий путь на репрезентативных входах и утверждающий, что --trace-deopt пуст, а %GetOptimizationStatus сообщает оптимизированный уровень; роняйте CI на любой регрессии.
- Воспроизведите в миниатюре один из реальных провалов юнита (polymorphic render-IC в стиле Discord, периодический NaN-deopt в стиле Slack или megamorphism опционального поля в стиле Maps) и напишите однострочную первопричину и фикс выше по потоку.
- Снимите снимок кучи до и после варианта с утечкой замыкания (покадровое замыкание, захваченное долгоживущим колбэком) и покажите рост old gen, затем почините, вынеся замыкание наружу, и снимите снова для подтверждения.
Это тот цикл, который вы запускаете в каждом реальном инциденте V8: инструментируйте уровень и состояние IC через d8 с natives syntax и —trace-deopt, воспроизведите megamorphic-сайт и deopt-loop, чтобы их увидеть, почините форму и числовой тип выше по потоку в модели данных и подтвердите числами до/после на идентичном входе. Сделав это однажды на игрушечном скрипте, вы превратите прод-диагностику — где регрессия прячется в небольшом рефакторинге — в мышечную память.