Profile-Guided Optimization (PGO)

Archivo: src/pgo.rs (387 líneas)

¿Por qué existe?

Recolecta perfiles de ejecución reales y recompila guiada por perfil para optimizar las rutas que el programa realmente ejecuta. En lugar de optimizar a ciegas, PGO identifica funciones calientes, branches con patrón claro y loops con muchas iteraciones, y conduce decisiones como inlining, layout lineal de branches, unrolling agresivo y optimización tier-2.

Flujo CLI

forja run --pgo=recoger programa.fa   → genera .forjaprof
forja build --pgo=usar programa.fa    → recompila con el perfil

La primera corrida ejecuta instrumentada y guarda el perfil en .forjaprof (en la raíz del proyecto). Las corridas posteriores con --pgo=usar cargan el perfil existente y lo aplican sin volver a recolectar. En la VM ForjaFast, el flujo conecta ProfileManager (persistencia) → aplicar_pgo (guía la ejecución) → instrumentación → ProfileManager::save (merge del perfil nuevo con el previo).

Datos recolectados

CampoDescripción
function_hotnessConteo de llamadas por función (record_call)
branch_countsBranch taken/not-taken por block: (taken, not_taken) (record_branch)
loop_iterationsIteraciones totales por loop (record_loop)
type_distributionDistribución de tipos en inline caches (record_type_at_ic)
hot_ipsHotness por instruction pointer del bytecode, para pre-especialización adaptativa (record_ip)

Estructuras clave

ComponenteDescripción
ProfileDataPerfil completo, serializable a JSON (serde). serialize/deserialize usan serde_json
BranchHotnessClasificación de un branch: HotTaken (taken > 80%), HotNotTaken (taken < 20%) o Unpredictable
ProfileGuidedDecisionsDecisiones derivadas del perfil: inline_candidates, hot_branches, hot_loops y tier2_candidates
InstrumenterContadores por IP con sampling (sample_rate, cada N instrucciones); hot_paths y hottest_ip extraen los IPs más ejecutados
ProfileManagerPersistencia del archivo .forjaprof en el directorio del proyecto

Decisiones de optimización

ProfileGuidedDecisions::from_profile deriva automáticamente: funciones con >= 100 llamadas van a inline_candidates; funciones con >= 1000 llamadas a tier2_candidates (optimización agresiva); branches con patrón claro (no Unpredictable) y > 10 ejecuciones van a hot_branches (layout lineal con fall-through); loops con > 10000 iteraciones van a hot_loops (unrolling agresivo). Los métodos hot_branch_ips y hot_loop_back_edges extraen los IPs correspondientes para que la VM priorice quickening en ellos.

Cómo se integra

El JIT engine (jit_engine.rs) carga el perfil con cargar_perfil, usa pgo_loop_unroll_factor para el unrolling de loops calientes, y es_pgo_tier2_candidate para decidir optimizaciones agresivas por función. La VM aplica el perfil con aplicar_pgo antes de ejecutar y mergea los datos recolectados con los previos al finalizar (lib.rs::ejecutar_con_pgo_impl).

💡 La serialización actual usa JSON via serde_json. El código documenta alternativas (bincode, binario manual con endian fijo, postcard) para reducir tamaño (~2-5x actual) y acelerar la serialización.
⚠️ Los umbrales son heurísticos: 100 llamadas para inline, 1000 para tier-2, 10000 iteraciones para unrolling. Un perfil de una corrida atípica puede producir decisiones subóptimas; por eso el flujo mergea los perfiles entre corridas.