Skills Agentes

V8 Jit

Patrones de optimización del JIT de V8 para JavaScript de alto rendimiento en las tripas del servidor de Next.js: clases ocultas, llamadas monomórficas, deopt megamórfica y perfilado.

Oficial
Estrellas
142k

en todo el repo

Actividad
43

0–100, la ruta de este skill

Actualizado
hace 3 meses

último commit aquí

Commits
0

últimos 90 días

Contexto
3.6k tok

98 tok en reposo

Paquete
1 archivo

14 KB

Instalar

Funciona con cualquier agente que lea SKILL.md

npx -y skills add vercel/next.js --skill v8-jit --agent claude-code

Se instala solo en este repositorio.

Qué hace

  • Recoge patrones de optimización para el JIT de V8 al escribir JavaScript de alto rendimiento en las tripas del servidor de Next.js
  • Cubre clases ocultas o shapes, y la diferencia entre puntos de llamada monomórficos, polimórficos y megamórficos
  • Trata la presión de asignación por closures y la optimización de arrays empaquetados
  • Cubre operaciones de cadena y cuándo usar `Map` y `Set` frente a objetos planos
  • Explica el perfilado con `--trace-opt` y `--trace-deopt`, con lista de comprobación para revisar código de ruta caliente

Úsalo cuando

  • Escribir o revisar código de ruta caliente en `app-render`, `stream-utils`, enrutado, caché o cualquier código por petición

No lo uses cuando

    Qué lo activa

    Di cualquiera de estas frases y el agente debería cargar este skill.

    • Optimiza esta función de ruta caliente
    • ¿Esto provoca una deoptimización en V8?
    • Revisa este código por petición para rendimiento

    SKILL.md

    En inglés

    V8 JIT Optimization

    Use this skill when writing or optimizing performance-critical code paths in Next.js server internals — especially per-request hot paths like rendering, streaming, routing, and caching.

    Background: V8's Tiered Compilation

    V8 compiles JavaScript through multiple tiers:

    1. Ignition (interpreter) — executes bytecode immediately.
    2. Sparkplug — fast baseline compiler (no optimization).
    3. Maglev — mid-tier optimizing compiler.
    4. Turbofan — full optimizing compiler (speculative, type-feedback-driven).

    Code starts in Ignition and is promoted to higher tiers based on execution frequency and collected type feedback. Turbofan produces the fastest machine code but bails out (deopts) when assumptions are violated at runtime.

    The key principle: help V8 make correct speculative assumptions by keeping types, shapes, and control flow predictable.

    Hidden Classes (Shapes / Maps)

    Every JavaScript object has an internal "hidden class" (V8 calls it a Map, the spec calls it a Shape). Objects that share the same property names, added in the same order, share the same hidden class. This enables fast property access via inline caches.

    Initialize All Properties in Constructors

    // GOOD — consistent shape, single hidden class transition chain
    class RequestContext {
      url: string
      method: string
      headers: Record<string, string>
      startTime: number
      cached: boolean
    
      constructor(url: string, method: string, headers: Record<string, string>) {
        this.url = url
        this.method = method
        this.headers = headers
        this.startTime = performance.now()
        this.cached = false // always initialize, even defaults
      }
    }
    
    // BAD — conditional property addition creates multiple hidden classes
    class RequestContext {
      constructor(url, method, headers, options) {
        this.url = url
        this.method = method
        if (options.timing) {
          this.startTime = performance.now() // shape fork!
        }
        if (options.cache) {
          this.cached = false // another shape fork!
        }
        this.headers = headers
      }
    }
    

    Rules:

    • Assign every property in the constructor, in the same order, for every instance. Use null / undefined / false as default values rather than omitting the property.
    • Prefer factory functions when constructing hot-path objects. A single factory makes it harder to accidentally fork shapes in different call sites.
    • Never delete a property on a hot object — it forces a transition to dictionary mode (slow properties).
    • Avoid adding properties after construction (obj.newProp = x) on objects used in hot paths.
    • Object literals that flow into the same function should have keys in the same order:
    • Use tuples for very small fixed-size records when names are not needed. Tuples avoid key-order pitfalls entirely.
    // GOOD — same key order, shares hidden class
    const a = { type: 'static', value: 1 }
    const b = { type: 'dynamic', value: 2 }
    
    // BAD — different key order, different hidden classes
    const a = { type: 'static', value: 1 }
    const b = { value: 2, type: 'dynamic' }
    

    Real Codebase Example

    Span in src/trace/trace.ts initializes all fields in the constructor in a fixed order — name, parentId, attrs, status, id, _start, now. This ensures all Span instances share one hidden class.

    Monomorphic vs Polymorphic vs Megamorphic

    V8's inline caches (ICs) track the types/shapes seen at each call site or property access:

    IC State Shapes Seen Speed
    Monomorphic 1 Fastest — single direct check
    Polymorphic 2–4 Fast — linear search through cases
    Megamorphic 5+ Slow — hash-table lookup, no inlining

    Once an IC goes megamorphic it does NOT recover (until the function is re-compiled). Megamorphic ICs also prevent Turbofan from inlining the function.

    Keep Hot Call Sites Monomorphic

    // GOOD — always called with the same argument shape
    function processChunk(chunk: Uint8Array): void {
      // chunk is always Uint8Array → monomorphic
    }
    
    // BAD — called with different types at the same call site
    function processChunk(chunk: Uint8Array | Buffer | string): void {
      // IC becomes polymorphic/megamorphic
    }
    

    Practical strategies:

    • Normalize inputs at the boundary (e.g. convert BufferUint8Array once) and keep internal functions monomorphic.
    • Avoid passing both null and undefined for the same parameter — pick one sentinel value.
    • When a function must handle multiple types, split into separate specialized functions and dispatch once at the entry point:
    // Entry point dispatches once
    function handleStream(stream: ReadableStream | Readable) {
      if (stream instanceof ReadableStream) {
        return handleWebStream(stream) // monomorphic call
      }
      return handleNodeStream(stream) // monomorphic call
    }
    

    This is the pattern used in stream-ops.ts and throughout the stream-utils code (Node.js vs Web stream split via compile-time switcher).

    Closure and Allocation Pressure

    Every closure captures its enclosing scope. Creating closures in hot loops or per-request paths generates GC pressure and can prevent escape analysis.

    Hoist Closures Out of Hot Paths

    // BAD — closure allocated for every request
    function handleRequest(req) {
      stream.on('data', (chunk) => processChunk(chunk, req.id))
    }
    
    // GOOD — shared listener, request context looked up by stream
    const requestIdByStream = new WeakMap()
    function onData(chunk) {
      const id = requestIdByStream.get(this)
      if (id !== undefined) processChunk(chunk, id)
    }
    
    function processChunk(chunk, id) {
      /* ... */
    }
    
    function handleRequest(req) {
      requestIdByStream.set(stream, req.id)
      stream.on('data', onData)
    }
    
    // BEST — pre-allocate the callback as a method on a context object
    class StreamProcessor {
      id: string
      constructor(id: string) {
        this.id = id
      }
      handleChunk(chunk: Uint8Array) {
        processChunk(chunk, this.id)
      }
    }
    

    Avoid Allocations in Tight Loops

    // BAD — allocates a new object per iteration
    for (const item of items) {
      doSomething({ key: item.key, value: item.value })
    }
    
    // GOOD — reuse a mutable scratch object
    const scratch = { key: '', value: '' }
    for (const item of items) {
      scratch.key = item.key
      scratch.value = item.value
      doSomething(scratch)
    }
    

    Real Codebase Example

    node-stream-helpers.ts hoists encoder, BUFFER_TAGS, and tag constants to module scope to avoid re-creating them on every request. The bufferIndexOf helper uses Buffer.indexOf (C++ native) instead of a per-call JS loop, eliminating per-chunk allocation.

    Array Optimizations

    V8 tracks array "element kinds" — an internal type tag that determines how elements are stored in memory:

    Element Kind Description Speed
    PACKED_SMI Small integers only, no holes Fastest
    PACKED_DOUBLE Numbers only, no holes Fast
    PACKED_ELEMENTS Mixed/objects, no holes Moderate
    HOLEY_* Any of above with holes Slower (extra bounds check)

    Transitions are one-way — once an array becomes HOLEY or PACKED_ELEMENTS, it never goes back.

    Rules

    • Pre-allocate arrays with known size: new Array(n) creates a holey array. Prefer [] and push(), or use Array.from({ length: n }, initFn).
    • Don't create holes: arr[100] = x on an empty array creates 100 holes.
    • Don't mix types: [1, 'two', {}] immediately becomes PACKED_ELEMENTS.
    • Prefer typed arrays only when you need binary interop/contiguous memory or have profiling evidence that they help. For small/short-lived collections, normal arrays can be faster and allocate less.
    // GOOD — packed SMI array
    const indices: number[] = []
    for (let i = 0; i < n; i++) {
      indices.push(i)
    }
    
    // BAD — holey from the start
    const indices = new Array(n)
    for (let i = 0; i < n; i++) {
      indices[i] = i
    }
    

    Real Codebase Example

    accumulateStreamChunks in app-render.tsx uses const staticChunks: Array<Uint8Array> = [] with push() — keeping a packed array of a single type throughout its lifetime.

    Function Optimization and Deopts

    Hot-Path Deopt Footguns

    • arguments object: using arguments in non-trivial ways (e.g. arguments[i] with variable i, leaking arguments). Use rest params instead.
    • Type instability at one call site: same operation sees both numbers and strings (or many object shapes) and becomes polymorphic/megamorphic.
    • eval / with: prevents optimization entirely.
    • Highly dynamic object iteration: avoid for...in on hot objects; prefer Object.keys() / Object.entries() when possible.

    Favor Predictable Control Flow

    // GOOD — predictable: always returns same type
    function getStatus(code: number): string {
      if (code === 200) return 'ok'
      if (code === 404) return 'not found'
      return 'error'
    }
    
    // BAD — returns different types
    function getStatus(code: number): string | null | undefined {
      if (code === 200) return 'ok'
      if (code === 404) return null
      // implicitly returns undefined
    }
    

    Watch Shape Diversity in switch Dispatch

    // WATCH OUT — `node.type` IC can go megamorphic if many shapes hit one site
    function render(node) {
      switch (node.type) {
        case 'div':
          return { tag: 'div', children: node.children }
        case 'span':
          return { tag: 'span', text: node.text }
        case 'img':
          return { src: node.src, alt: node.alt }
        // Many distinct node layouts can make this dispatch site polymorphic
      }
    }
    

    This pattern is not always bad. Often the main pressure is at the shared dispatch site (node.type), while properties used only in one branch stay monomorphic within that branch. Reach for normalization/splitting only when profiles show this site is hot and polymorphic.

    String Operations

    • String concatenation in loops is usually fine in modern V8 (ropes make many concatenations cheap). For binary data, use Buffer.concat().
    • Template literals vs concatenation: equivalent performance in modern V8, but template literals are clearer.
    • string.indexOf() > regex for simple substring checks.
    • Reuse RegExp objects: don't create a new RegExp() inside a hot function — hoist it to module scope.
    // GOOD — regex hoisted to module scope
    const ROUTE_PATTERN = /^\/api\//
    
    function isApiRoute(path: string): boolean {
      return ROUTE_PATTERN.test(path)
    }
    
    // BAD — regex recreated on every call
    function isApiRoute(path: string): boolean {
      return /^\/api\//.test(path) // V8 may or may not cache this
    }
    

    Map and Set vs Plain Objects

    • Map is faster than plain objects for frequent additions/deletions (avoids hidden class transitions and dictionary mode).
    • Set is faster than obj[key] = true for membership checks with dynamic keys.
    • For static lookups (known keys at module load), plain objects or Object.freeze({...}) are fine — V8 optimizes them as constant.
    • Never use an object as a map if keys come from user input (prototype pollution risk + megamorphic shapes).

    Profiling and Verification

    V8 Flags for Diagnosing JIT Issues

    # Trace which functions get optimized
    node --trace-opt server.js 2>&1 | grep "my-function-name"
    
    # Trace deoptimizations (critical for finding perf regressions)
    node --trace-deopt server.js 2>&1 | grep "my-function-name"
    
    # Combined: see the full opt/deopt lifecycle
    node --trace-opt --trace-deopt server.js 2>&1 | tee /tmp/v8-trace.log
    
    # Show IC state transitions (verbose)
    node --trace-ic server.js 2>&1 | tee /tmp/ic-trace.log
    
    # Print optimized code (advanced)
    node --print-opt-code --code-comments server.js
    

    Targeted Profiling in Next.js

    # Profile a production build
    node --cpu-prof --cpu-prof-dir=/tmp/profiles \
      node_modules/.bin/next build
    
    # Profile the server during a benchmark
    node --cpu-prof --cpu-prof-dir=/tmp/profiles \
      node_modules/.bin/next start &
    # ... run benchmark ...
    # Analyze in Chrome DevTools: chrome://inspect → Open dedicated DevTools
    
    # Quick trace-deopt check on a specific test
    node --trace-deopt $(which jest) --runInBand test/path/to/test.ts \
      2>&1 | grep -i "deopt" | head -50
    

    Using % Natives (Development/Testing Only)

    With --allow-natives-syntax:

    function hotFunction(x) {
      return x + 1
    }
    
    // Force optimization
    %PrepareFunctionForOptimization(hotFunction)
    hotFunction(1)
    hotFunction(2) % OptimizeFunctionOnNextCall(hotFunction)
    hotFunction(3)
    
    // Check optimization status
    // 1 = optimized, 2 = not optimized, 3 = always optimized, 6 = maglev
    console.log(%GetOptimizationStatus(hotFunction))
    

    Checklist for Hot Path Code Reviews

    • All object properties initialized in constructor/literal, same order
    • No delete on hot objects
    • No post-construction property additions on hot objects
    • Functions receive consistent types (monomorphic call sites)
    • Type dispatch happens at boundaries, not deep in hot loops
    • No closures allocated inside tight loops
    • Module-scope constants for regex, encoders, tag buffers
    • Arrays are packed (no holes, no mixed types)
    • Map/Set used for dynamic key collections
    • No arguments object — use rest params
    • try/catch at function boundary, not inside tight loops
    • String building via array + join() or Buffer.concat()
    • Return types are consistent (no string | null | undefined mixes)

    Related Skills

    • $dce-edge — DCE-safe require patterns (compile-time dead code)
    • $runtime-debug — runtime bundle debugging and profiling workflow

    Reproducido de vercel/next.js bajo licencia MIT. Leer esta página en markdown.

    Archivos

    1 archivo en el paquete. Solo se lee SKILL.md al activarse — las referencias se cargan si el skill decide que las necesita.

    Antes de instalar

    Es un skill interno del repositorio de Next.js.

    Necesita en el PATH:node

    Detalles

    Creador
    vercel
    Licencia
    MIT
    Recursos incluidos
    Solo SKILL.md
    Repositorio
    vercel/next.js
    Código fuente
    Ver SKILL.md

    Etiquetas

    Más de vercel/next.js

    Este repo incluye 22 skills. Si instalas uno, normalmente ya tienes los demás.

    Activa Cache Components en una app de Next.js y resuelve las rutas bloqueantes que aparecen. Úsalo para adoptar o migrar, activar el flag cacheComponents o decidir entre excluir rutas y arreglarlas.

    Costo de contexto al activarse
    8.1k tok
    Tamaño del paquete
    3 archivos
    Última actualización
    hace 3 días
    Oficialherramientas desarrollo

    Lleva una ruta de Next.js a navegación instantánea bajo Cache Components o PPR mediante un bucle agéntico: codifica el objetivo como un e2e instant() en rojo y lo trabaja hasta verde, ruta a ruta.

    Costo de contexto al activarse
    6.3k tok
    Tamaño del paquete
    6 archivos
    Última actualización
    hace 5 días
    Oficialherramientas desarrollo

    Activa Partial Prefetching en una app de Next.js y resuelve las insights que surgen: audita los Link con prefetch, activa partialPrefetching y opta por rutas con prefetch = 'partial'.

    Costo de contexto al activarse
    6k tok
    Tamaño del paquete
    1 archivo
    Última actualización
    hace 3 días
    Oficialherramientas desarrollo

    Verifica el comportamiento en runtime de Next.js tras editar código de la aplicación. Combina /_next/mcp, la visión de Next.js, con agent-browser, la del navegador. Requiere un next dev en marcha.

    Costo de contexto al activarse
    2k tok
    Tamaño del paquete
    1 archivo
    Última actualización
    hace 5 días
    Oficialtesting qa

    Gestiona PRs apilados y parte el trabajo en ramas revisables con gh-stack: creación, visualización, edición, push, envío, sincronización, rebase, merge y checkout.

    Costo de contexto al activarse
    2.3k tok
    Tamaño del paquete
    4 archivos
    Última actualización
    hace 12 días
    Oficialherramientas desarrollo

    Compara el rendimiento de cambios de React o Next.js en VMs de Vercel Sandbox con estadística A/B pareada: rps, latencia, p95, TTFB, RSS y bytes de documento y Flight.

    Costo de contexto al activarse
    4.1k tok
    Tamaño del paquete
    13 archivos
    Última actualización
    hace 19 días
    Oficialtesting qa

    Skills relacionados

    Cómo crear y mantener skills de agente en .agents/skills/. Úsalo al crear un SKILL.md, escribir descripciones, elegir campos de frontmatter o decidir qué va en un skill y qué en AGENTS.md.

    Costo de contexto al activarse
    1k tok
    Tamaño del paquete
    1 archivo
    Última actualización
    hace 3 meses
    Oficialherramientas desarrollo

    Lleva un pull request fusionado de Next.js desde canary a una rama de release anterior como next-16-2: localiza el commit, crea la rama, hace cherry-pick, valida y abre el PR.

    Costo de contexto al activarse
    832 tok
    Tamaño del paquete
    1 archivo
    Última actualización
    el mes pasado
    Oficialherramientas desarrollo

    Crea ramas, commits, pushes y pull requests de GitHub para Next.js. Cubre la plantilla de PR, el formato de --body, las ramas codex/ y las directivas de git de la app Codex.

    Costo de contexto al activarse
    944 tok
    Tamaño del paquete
    1 archivo
    Última actualización
    el mes pasado
    Oficialherramientas desarrollo