# firefox-pgo-profile — el perfil de ejecución que optimiza a firefox. UNA MEDICIÓN CONGELADA. # # ══ QUÉ ES ═════════════════════════════════════════════════════════════════════════════════════ # `merged.profdata`: 501.521 funciones, 3.734.991 bloques y **38.498.366.335 ejecuciones** # registradas sobre un corpus de **46 páginas**: las 36 de Mozilla (`build/pgo`) MÁS las 10 de # maquetación propias de `scripts/pgo-corpus/`. # # ⚠ EL CORPUS SE AMPLIÓ POR UNA MEDICIÓN, NO POR CORAZONADA. Con sólo las 36 de Mozilla la ganancia # medida fue **−9,4 %** en una página de DOM/maquetación y **−1,0 %** en `3d-raytrace`. La razón es # que un benchmark JIT-bound es casi CIEGO al PGO —el bucle caliente lo ejecuta código que el JIT # emite en runtime, y el PGO optimiza el intérprete, el GC y el propio JIT, no lo que el JIT # produce—, y el corpus de Mozilla está dominado en número de páginas por SunSpider. O sea que # entrenaba mucho justo donde el PGO menos rinde. Las 10 nuevas van al camino que sí es C++ de punta # a punta: flexbox, grid, tablas, texto en columnas, selectores, pintado, transforms, SVG, scroll # pegajoso y layout thrashing. Efecto medido en el propio perfil: las ejecuciones registradas pasan # de 5.885.254.799 a 38.498.366.335 (**6,5×**). # `firefox` lo consume con `--enable-profile-use=cross`. # # ══ POR QUÉ ES UNA FUENTE PINEADA Y NO UNA RECETA QUE LO GENERA ════════════════════════════════ # **El perfil NO ES DETERMINISTA**: los contadores dependen del timing de la corrida, así que dos # generaciones no dan los mismos bytes. Si esto fuera una receta que lo produce, su ArtifactHash # —input-addressed— no se movería entre generaciones, y firefox consumiría bytes distintos en la # misma dirección: el build optimizado dejaría de reproducir sin que nada lo dijera. Es el mismo # modo de fallo que el lab fuera de `hash_inputs`, fabricado a propósito. # # La salida es la que el SDD 26 §3.ter ya elegía: **generar el perfil UNA vez y sellarlo**, aquí como # fuente pineada por sha256. Así el insumo es no determinista pero el build vuelve a serlo, que es # exactamente lo que se quiere. Regenerarlo es una decisión explícita —subir un blob nuevo y cambiar # el sha256—, nunca un accidente. # # ⚠ NO TIENE UPSTREAM, Y LA URL LO DICE. El blob es nuestro: sale de nuestra corrida sobre nuestro # binario. La URL `.invalid` no resuelve por DNS a propósito (es el patrón que el ADR 0013 ya usa y # prueba); los bytes vienen del mirror de fuentes, que sirve por sha256. Como la URL nunca estuvo en # `hash_inputs` (ADR 0013), mover el objeto de origen no re-hashea nada. # # Publicado el 2026-09-07 en `takana/fuentes/.tar` del Storage Box # (`scripts/fuentes/mirror-env.sh` trae `TAKANA_MIRROR` y su clave). # Verificado bajándolo de vuelta por el mismo camino que usa takana: 17.426.112 bytes, sha256 ok. # # ⚠ SI SE RECONSTRUYE `firefox-instrumentado`, ESTE PERFIL NO CADUCA SOLO. Un perfil recogido de un # binario que ya no se parece al que se optimiza sigue siendo válido para clang —los contadores se # aplican por nombre de función— pero vale menos: las funciones que ya no existen se ignoran y las # nuevas quedan sin datos. Regenerarlo es su propia unidad de trabajo, no un efecto secundario. # ⚠ EL JARLOG SE PROBÓ, FUNCIONA, Y ESTÁ APARCADO CON LA EVIDENCIA MEDIDA (2026-09-08). # Se generó (`MOZ_JAR_LOG_FILE` en el entorno; NO hace falta la extensión `Quitter` de Mozilla, que # el SDD daba por bloqueante), entró en el build, y se verificó del lado del artefacto: el `libxul` # quedó IDÉNTICO byte a byte y sólo cambió el `omni.ja`. O sea que la función existe. # # Pero **convierte el `omni.ja` al formato «jar optimizado» de Mozilla**, que mueve el directorio # central al principio: # # sin jarlog: PK\003\004 ZIP estándar — `zipfile` lo abre, 5306 entradas # con jarlog: \376\204#\0 `zipfile` lo rechaza: BadZipFile # # Y eso ROMPE `atuq`, que reempaqueta el `omni.ja` con `zipfile` para su branding: # `zipfile.BadZipFile: Bad magic number for central directory` en `rebrand.py`. # # La cuenta es asimétrica y por eso se aparca: el COSTE está MEDIDO —el navegador propio de la # distro no se puede construir— y el BENEFICIO no. El banco corre con caché de página caliente, que # es donde reordenar el `omni.ja` no ahorra ninguna lectura, y dio −0,1 %: por debajo del suelo de # ruido del propio banco (~1 % entre corridas de días distintos). Cambiar algo que funciona por una # ganancia que no se pudo medir, rompiendo algo que sí funcionaba, es mal negocio. # # Se retoma cuando (1) se mida el arranque EN FRÍO, que es su terreno, y (2) `rebrand.py` sepa leer # el jar optimizado o des-optimizarlo antes. **El blob `92497cdd…` del mirror YA TRAE el jarlog**, # así que retomarlo no exige volver a perfilar: es cambiar una línea y re-pinear el sha256. # name = "firefox-pgo-profile" version = "2026.09.08" license = "MPL-2.0" [source] tarball = "https://no-hay-upstream.invalid/hammer/firefox-pgo-2026-09-08.tar.xz" sha256 = "95472411ba252e14c4d57de7193d53e4ba35fbccf4819ba95ceff1ca843e4a7a" # ⚠ `0` Y NO EL DEFAULT. takana recorta UN componente al extraer, porque los releases GNU vienen # con un `proyecto-version/` de más. Este tarball es nuestro y lleva el fichero en la raíz, así que # con el default se recortaba lo único que hay y el install moría en `cp: cannot stat # merged.profdata`. El error no menciona el recorte por ningún lado. strip_components = 0 [build] compiler = "clang" [build.phases] configure = "true" compile = "true" install = ''' set -e mkdir -p /out/usr/share/firefox-pgo cp merged.profdata /out/usr/share/firefox-pgo/merged.profdata P=/out/usr/share/firefox-pgo/merged.profdata # ── GUARDIÁN: QUE SEA UN PERFIL, NO UN FICHERO GRANDE ──────────────────────────────────────── # «Existe» no es «sirve», y acá el modo de fallo es especialmente callado: `--enable-profile-use` # con un profdata vacío o de otra versión no rompe el build de firefox — lo hace más lento y ya. # O sea que un perfil malo sella en verde, reproduce, y lo único que se pierde es justo lo que se # fue a buscar. Se le pregunta a `llvm-profdata` cuántas funciones trae. n=$(llvm-profdata show "$P" 2>/dev/null | sed -n 's/^Total functions: //p') case "${n:-0}" in ''|*[!0-9]*) n=0 ;; esac test "$n" -gt 100000 || { echo "!! el profdata declara $n funciones (esperaba >100000): perfil vacío o ilegible" >&2 llvm-profdata show "$P" 2>&1 | head -5 >&2 exit 1 } echo "guardián: perfil válido — $n funciones, $(stat -c%s "$P") bytes" '''