Браузер и фронтенд-рантайм
Спекулятивный движок TurboFan и ловушка deopt-loop
Критичная для производительности функция повторно deopt’нется в production — каждый вызов запускает 100мс перекомпиляцию TurboFan, затем снова deopt. Ваш горячий путь на порядки медленнее, чем некомпилированный. Это deopt-loop, и он начинается с одной неправильно понятой защиты.
Спекулятивный движок оптимизации TurboFan
TurboFan строит sea-of-nodes граф IR и выполняет:
- Агрессивный инлайнинг — точки вызова с известными целями встраиваются, устраняя накладные расходы вызова и открывая возможности кросс-функциональных оптимизаций.
- Escape-анализ — объекты, не выходящие за пределы функции, выделяются на стеке или вовсе устраняются (ноль аллокаций, нет давления на GC).
- Полиморфный инлайнинг — до 4 известных целей вызова встраиваются с предварительной проверкой класса; общий медленный путь обрабатывает редкие формы.
- Анализ диапазонов — переменная, известная как 0..255, остаётся в байтовом регистре; индекс цикла с известными границами избегает проверок переполнения.
- Сужение типов — итеративное уточнение типов через граф, позволяющее создавать более точный машинный код.
При каждом спекулятивном предположении TurboFan устанавливает инструкцию защиты: «это smi» перед арифметикой, «этот объект имеет hidden class HCx» перед доступом к свойству. Сбой защиты означает deopt. Компромисс: код TurboFan часто в 3–10× быстрее Maglev при соблюдении защит; при их нарушении накладные расходы хуже, чем никогда не компилировать через TurboFan.
FeedbackVector подробно
У каждой функции есть связанный FeedbackVector — массив фиксированного размера в куче GC, по одному слоту на IC-сайт (загрузка свойства, вызов, бинарная операция). Слоты хранят:
- Один указатель на hidden class (monomorphic)
- Небольшой массив классов (polymorphic)
MegamorphicSentinel(сдался)
Все четыре уровня читают его: Sparkplug читает минимальный feedback (тип), Maglev читает более богатые данные (цепочки классов, частоты целей вызовов), TurboFan читает всё плюс количество итераций цикла и вероятности ветвлений.
Загрязнение FeedbackVector: один megamorphic-слот объясняет, почему V8 не может держать функцию оптимизированной даже после deopt-и-reopt циклов. %DebugPrintFeedback(fn) в d8 выводит вектор; его инспекция — отправная точка для диагностики постоянного deopt-поведения.
Sparkplug: JIT, который не оптимизирует
Sparkplug (V8 9.1, май 2021) генерирует машинный код из байткода за один линейный проход без SSA, инлайнинга или планирования инструкций. Для каждого байткода Ignition Sparkplug генерирует фиксированный блок нативных инструкций — примерно в 1.5–2× быстрее Ignition, так как накладные расходы цикла диспетчеризации интерпретатора (поиск в таблице, косвенный прыжок) заменяются прямым выполнением. Скорость компиляции: ~1мс/кБ байткода. Задокументированная средняя польза: 5–15% на реальных нагрузках. Цель: дать горячим-но-не-достаточно-горячим-для-Maglev функциям ускорение без оплаты стоимости компиляции Maglev.
Maglev: SSA, но дёшево
Maglev (2023) заполняет пробел между Sparkplug и TurboFan. Конвейер: байткод → Maglev IR (SSA) → линейный распределитель регистров → нативный код. IR проще, чем у TurboFan (без цепочек эффектов, без проходов перезаписи графа); распределитель регистров линейного сканирования вместо раскраски графа. Maglev выполняет некоторые спекулятивные оптимизации — специализирует загрузки свойств и арифметику по наблюдаемым типам — но пропускает escape-анализ, полиморфный инлайнинг и итерацию сужения типов. Результат: ~10мс компиляция, ~50–70% качества кода TurboFan, правильный компромисс для средне горячего кода.
- Sparkplug отгружен
- V8 9.1 (май 2021)
- Maglev отгружен
- V8 11.0 (середина 2023)
- Время компиляции TurboFan
- ~100 мс / функция
- Время компиляции Maglev
- ~10 мс / функция
- Размер IC-слота
- 16 байт (8Б класс + 8Б хендлер)
- Скорость компиляции Sparkplug
- ~1 мс / кБ байткода
- TurboFan vs Maglev скорость
- TurboFan ~1.5–3× быстрее при стабильных защитах
Критичная для производительности функция deopt-loop'ится в production. Трасируйте и исправьте.
Диагностируйте лог deopt V8 — в чём первопричина?
[deoptimizing (DEOPT eager): begin 0x... <JSFunction processItem (sfi = 0x...)> (opt #42) @14, FP to SP delta: 128, caller sp: 0x...]
;;; deoptimize at <main.js:42:18>, not a Smi
bytecode position 14
[deoptimizing (eager): end 0x... processItem @14 => node=4, pc=0x..., caller sp=0x..., took 0.012 ms]
[marking 0x... <JSFunction processItem (sfi = 0x...)> for non-concurrent optimization]
[compiling method 0x... <JSFunction processItem (sfi = 0x...)> using TurboFan]
[deoptimizing (DEOPT eager): begin 0x... <JSFunction processItem (sfi = 0x...)> (opt #43) @14, FP to SP delta: 128, caller sp: 0x...]
;;; deoptimize at <main.js:42:18>, not a Smi
bytecode position 14 Одна функция продолжает deopt'иться с причиной 'not a Smi' в строке 42:18. Что происходит и как это исправить?
V8 имеет ЧЕТЫРЕ JIT-уровня и конкурентный GC. Какова глубинная причина производительности, почему они необходимы вместо одного хорошего оптимизатора?
Функция обрабатывает 10М элементов на кадр и monomorphic в тестах, но megamorphic в production. Какова наиболее вероятная причина?
Почему это работает
Почему TurboFan использует sea-of-nodes IR вместо традиционного CFG с базовыми блоками? Sea-of-nodes представляет как поток управления, так и поток данных как рёбра в одном графе — нет понятия «порядок инструкций» до планирования. Это позволяет более агрессивные оптимизации: escape-анализ может поднять аллокации из циклов, анализ диапазонов распространяет границы через ветки, а устранение мёртвого кода работает на уровне выражений, а не только базовых блоков. Цена: компилятор значительно сложнее и труднее для отладки. Но для динамически типизированного языка, где компилятор должен рассуждать о типах, наблюдаемых во время выполнения, а не объявленных во время компиляции, гибкость стоит этого.
- 01Что такое sea-of-nodes IR TurboFan и какие оптимизации он позволяет?
- 02Объясните 'загрязнение FeedbackVector' и как его диагностировать.
- 03Чем deopt-loop отличается от разового deopt?
TurboFan строит sea-of-nodes IR и применяет escape-анализ, полиморфный инлайнинг, сужение диапазонов и агрессивную специализацию типов. Каждое спекулятивное предположение становится инструкцией защиты; сбой защиты вызывает деоптимизацию. Единичный deopt стоит микросекунды; deopt-loop — где TurboFan перекомпилирует и deopt’ится при каждом вызове — стоит дороже, чем никогда не оптимизировать. FeedbackVector — профильные данные, движущие каждым уровнем: Ignition пишет его, TurboFan читает для знания форм и типов специализации. Загрязнение FeedbackVector (megamorphic-слоты) может полностью заблокировать TurboFan. Sparkplug (V8 9.1) — базовый уровень стоимостью ~1мс/кБ, дающий 1.5–2× над Ignition; Maglev (2023) добавляет SSA-специализацию за ~10мс компиляции, достигая 50–70% качества TurboFan. Правильная ментальная модель: уровни — не водопад, а лестница с лифтами — функции могут быть на разных уровнях одновременно, а эвристики тиеризации постоянно пересматриваются на основе данных FeedbackVector.
встречается в143
- Почему GraphQL получает N+1junior
- Механика DataLoader: батчинг на границе тикаmiddle
- Контракты batch-функции: порядок, формы, ошибкиmiddle
- Federation и lookahead: батчинг за пределами DataLoadermiddle
- Защита сложности запросов: depth, cost, persisted queriesmiddle
- Senior GraphQL API: scheduling-контракт, изоляция арендаторов, наблюдаемостьsenior
- Зачем идемпотентность: безопасные retryjunior
- Серверный state machine: четыре состояния idempotency keymiddle
- Outbox и inbox: effectively-once через dual-write границуmiddle
- Конкурентность и архитектура кеша для идемпотентности на масштабеsenior
- Наблюдаемость, production-инциденты и дизайн для глобального масштабаsenior
- Что такое cache stampede и почему он делает всё хужеjunior
- Лок и single-flight: ограничение параллельных rebuildmiddle
- XFetch: вероятностное раннее истечение без координацииmiddle
- Stale-while-revalidate и CDN request coalescingmiddle
- Детектирование stampede и дизайн TTL для продакшенаmiddle
- Метастабильный сбой, fencing-токены и production-постмортемыsenior
- Что такое отношение: таблицы, строки, ключи и ограниченияjunior
- Ограничения, ключи и типы данных Postgresmiddle
- Нормальные формы, денормализация и почему схемы «прилипают»middle
- JSONB, массивы и когда side table побеждаетmiddle
- Heap-хранилище, TOAST и выравнивание колонокsenior
- Целостность схемы: deferral, версионирование и сбои в продакшнеsenior
- Реляционная модель vs документные, wide-column, граф и key-valuesenior
- Index-only scan, Visibility Map и INCLUDEsenior
- Типичные сбои в продакшне и аудит индексовsenior
- pg_statistic, ANALYZE и производственная наблюдаемостьmiddle
- Производственные режимы отказа и стабильность плановsenior
- MVCC: как Postgres раздаёт согласованные снимкиjunior
- Заголовок tuple и механика снимковmiddle
- HOT-обновления и уровни изоляцииmiddle
- VACUUM, bloat и autovacuummiddle
- CLOG, XID wraparound и MultiXactsenior
- SSI и production-тюнинг autovacuumsenior
- Реальные провалы MVCC, deployment-паттерны и распределённые снимкиsenior
- Connection pool: зачем амортизировать стоимость backend Postgresjunior
- Режимы PgBouncer: session, transaction и statementmiddle
- Размер пула: формула (ядра × 2) + шпинделей и двухуровневый стекmiddle
- Исчерпание пула и idle-in-transaction: сценарий отказа в 3 ночиmiddle
- Миграция на transaction mode: план развёртывания и prepared statements в PgBouncer 1.21middle
- Процессная модель Postgres и почему увеличение max_connections снижает производительностьsenior
- Ландшафт пулеров 2026, serverless connection storms и полная таксономия отказовsenior
- Что такое миграция схемы и почему она заменяет ad-hoc DDLjunior
- ADD COLUMN: мгновенно в PG 11+ против перезаписи в старом Postgresjunior
- Режим отказа очереди блокировок: почему мгновенный DDL может заморозить базуmiddle
- Безопасные DDL-паттерны: NOT VALID, CONCURRENTLY и исправления небезопасных операцийmiddle
- Expand-contract: нулевой простой для ломающих изменений схемыmiddle
- Advisory-блокировки, инструменты миграций и координация деплояsenior
- Таксономия сбоев миграций и дисциплина продакшнаsenior
- Зачем нужно шардирование: потолок одного Postgresjunior
- Выбор ключа шарда: стратегии hash, range, list и directorymiddle
- Партиционирование против шардирования: одно слово, два разных понятияmiddle
- Ко-локация и Citus: инвариант, делающий шардирование пригодным к использованиюmiddle
- Режим отказа hot shard: обнаружение, изоляция и долгосрочная политикаmiddle
- Schema-based шардирование и альтернативы мультиарендностиsenior
- Онлайн-решардинг, 2PC и операционная стоимость шардированияsenior
- Семь актов: от CREATE TABLE до Citusjunior
- Акты 1–3 в глубину: схема, индексы и статистика планировщикаmiddle
- Акты 4–6 в глубину: MVCC bloat, connection pooling и безопасные миграцииmiddle
- Акт 7 в глубину: шардинг, co-location и семиуровневый каскад трейдоффовmiddle
- Наблюдаемость, антипаттерны и производственный триажsenior
- Роли Raft, term и почему majority-кворум предотвращает split brainjunior
- Как Raft реплицирует log entry и решает, что его безопасно коммититьmiddle
- Выборы лидера в Raft: таймауты, правила голосования и четыре свойства безопасностиmiddle
- Raft в реальном мире: partition, медленный диск и клиентская маршрутизацияmiddle
- Расширения Raft: pre-vote, learner, snapshot и линеаризуемые чтенияsenior
- Raft в production: membership change, Multi-Raft и observabilitysenior
- Где происходит data fetching — и почему это решает LCPjunior
- Fetch waterfall''''ы — диагностика и лечение через Promise.allmiddle
- React Server Components и Suspense streamingmiddle
- Клиентский кэш: TanStack Query, SWR и stale-while-revalidatemiddle
- LCP, prefetch и race conditions в интерактивном fetchingmiddle
- Senior internals: RSC payload, слои кэша и production паденияsenior
- Трёхстороннее рукопожатие TCPjunior
- Номера последовательности и состояние соединенияmiddle
- DNS: что делает и зачем существуетjunior
- Обход резолвера: перенаправления, типы записей и gluemiddle
- TTL, кеширование и распространение DNSmiddle
- Рукопожатие за 1 RTT: key share и ECDHEmiddle
- Возобновление сессии и 0-RTTmiddle
- WebSocket: HTTP-апгрейд до постоянного соединенияjunior
- Формат WebSocket-фрейма: opcodes, маскирование, фрагментацияmiddle
- Backpressure в WebSocket: когда клиенты не успеваютmiddle
- Реконнект: jittered backoff, thundering herd, восстановление сообщенийsenior
- WebSocket в масштабе: HTTP/2 мультиплексирование, permessage-deflate, C10Msenior
- WebSocket в production: прокси, безопасность и распределённая архитектураsenior
- Что делают обратные проксиjunior
- Health checks, connection draining и slow startmiddle
- Session affinity, consistent hashing и правильное решениеmiddle
- Retry-бури, circuit breakers и load sheddingsenior
- Устойчивая архитектура LB: anycast, zone-aware маршрутизация и observabilitysenior
- Почему QUIC, а не TCP+TLSjunior
- Connection ID и миграция сетиmiddle
- Возобновление 0-RTT и шифрование пакетовsenior
- DDoS: что это и почему работаетjunior
- Атаки усиления и истощение состоянияmiddle
- Ограничение скорости: алгоритмы и архитектураmiddle
- WAF, межсетевые экраны, mTLS и HSTSmiddle
- Отравление DNS-кэша и BGP-перехватsenior
- Эшелонированная защита и экономика атакsenior
- DNS, TCP, TLS по очереди: куда уходят миллисекундыmiddle
- Перехват прокси и шлюзы безопасности: rate limiter, WAF, mTLSmiddle
- Альтернативные пути: QUIC 0-RTT, WebSocket upgrade, миграция соединенияmiddle
- Наблюдаемость: распределённые трейсы, USE/RED и семплированиеsenior
- Устойчивость: каскадные повторы, circuit breakers и error budgetsenior
- Что такое три сигнала: метрики, логи, трейсыjunior
- Зачем нужны структурные логи: дневник против таблицыjunior
- Схема продакшн-лога: поля, которые несёт каждая строкаmiddle
- PII-редакция и log injectionsenior
- OTel Logs Data Model и audit-логи как подсистемаsenior
- SLI, SLO и error budget: надёжность в числахjunior
- Error budget policy, latency SLO и составные journeysmiddle
- Продакшн-отказы SLO, самонаблюдаемость, безопасность и общая картинаsenior
- Петля инцидента: от пейджера до постмортема до предотвращенияmiddle
- Cache lines и false sharing: когда параллелизм замедляет кодmiddle
- SIMD и data layout: AoS vs SoA и разница в 4–8xmiddle
- Cache-oblivious алгоритмы, PGO и production failuressenior
- GC в production: наблюдаемость, безопасность, edge cases и управление флотомsenior
- Batching: амортизируй фиксированную цену каждой операцииjunior
- Окно батчинга: размер и время ожиданияmiddle
- Batching в Kafka и Postgresmiddle
- io_uring и наблюдаемость пакетированияmiddle
- От Nagle до io_uring: эволюция пакетированияmiddle
- Backpressure, изоляция сбоев и безопасность батчей в продакшенеsenior
- CI enforcement и RUM: делаем бюджеты рабочимиmiddle
- V8 JIT-пайплайн, HTTP-приоритеты и безопасность bundlesenior
- Цикл performance: дисциплина, а не проектjunior
- Классификация и исправление: сопоставление family bottleneck с методамиmiddle
- Observability-стек и CI gates: ловить регрессии до выпускаmiddle
- От инцидента к enforcement: SLO burn до верифицированного исправления за 35 минутmiddle
- Культура, экономика и масштаб performancesenior
- At-most-once, at-least-once, exactly-once: три контракта доставкиjunior
- Три ножки сбоя — где реально происходят дубликаты и потериmiddle
- Consumer-side dedup: самый дешёвый путь к exactly-once processingmiddle
- Kafka exactly-once semantics: idempotent producer и транзакцииmiddle
- SQS visibility timeout, DLQ и outbox patternmiddle
- Exactly-once в production: impossibility-доказательство, гибридные паттерны и реальные инцидентыsenior
- Что такое OAuth и почему пароли — не ответjunior
- Authorization code flow с PKCEmiddle
- Валидация ID-токена и управление JWKS-кешемmiddle
- Ротация refresh-токенов и scope-based least privilegemiddle
- Sender-constrained токены: DPoP и mTLSsenior
- OAuth в production: audience атаки, observability и реальные провалыsenior