Files
takana/docs/plan-variantes-cpu.md
T

98 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Plan — variantes por nivel de CPU (x86-64-v3) sobre el CAS
**Fecha:** 2026-07-16 · **Origen:** el linaje Solus (hwcap dirs AVX2 ~2017, lista corta medida)
→ Red Hat (niveles x86-64-v2/v3/v4 + `glibc-hwcaps` en glibc 2.33, selección en el loader)
→ CachyOS (repos enteros v3/v4 + LTO + scheduler). Análisis de qué forma toma eso en hammer.
**Tesis:** hammer ya tiene la mitad difícil gratis. Una variante optimizada no es un mecanismo
nuevo: es **la misma receta con otros `flags`**, que sella a otro hash en el mismo store. Lo que
las distros binarias resuelven con subdirectorios mágicos del loader, hammer lo resuelve con lo
que ya es: contenido direccionado por hash + hydrate.
---
## 1. La restricción que ordena todo el diseño: musl no tiene hwcaps
El mecanismo `glibc-hwcaps` es del loader de **glibc**. El ldso de musl no busca en
subdirectorios por microarquitectura y no va a hacerlo. Conclusión estructural:
> **La selección de variante en hammer ocurre en HYDRATE (por máquina), no en runtime (por
> proceso).** Cada máquina hidrata la variante que su CPU soporta; el árbol instalado tiene UNA
> copia de cada lib, la correcta. Sin dispatch, sin dobles copias en disco, sin loader parcheado.
Esto no es una limitación disfrazada: es el modelo más simple de los dos, y el único compatible
con el resto de hammer (un artefacto = un hash = un contenido; "elige en runtime" rompería la
correspondencia instalado↔sellado que usa todo, del journal a CVE-por-grafo).
Costo honesto: una imagen/USB "para cualquier máquina" debe llevar baseline (o dos hydrates).
El punto de selección es `hammer hydrate` / el instalador, con sonda de CPU (`cpuid` → nivel).
## 2. Mecanismo: variante = flags, cero framework nuevo
Hoy el sandbox fija `CC="zig cc -mcpu=baseline"` (`sandbox.rs:415`) precisamente para NO hornear
la ISA del builder. Una variante v3 es el mismo contrato con otro valor pineado:
- **T1 — campo `variant` en la receta** (o sección `[variants.v3]` que sólo sobreescribe
`flags`/`mcpu`). Regla dura: la variante **hereda todo lo demás** de la receta canónica —
misma fuente, mismos patches, mismas deps. Si diverge en algo más, no es una variante, es
otra receta.
- **T2 — `mcpu=x86_64_v3` pineado por nombre de nivel**, nunca `native` (mismo argumento que
baseline: la ISA del builder no puede filtrarse; el nivel es el contrato).
- **T3 — el índice firmado publica `(nombre, versión, nivel, hash)`**. El nivel es un eje más
del catálogo, no un repo aparte (donde CachyOS mantiene N repos, hammer tiene N hashes).
- **T4 — sonda de nivel en hydrate/instalador**: detectar v2/v3/v4 y resolver el hash
correspondiente, con fallback a baseline. Idempotente como todo hydrate.
La bit-repro no se toca: la reproducibilidad es por-(receta, flags); una variante v3 reproduce
contra sí misma en la granja exactamente igual que hoy reproduce baseline.
## 3. Alcance: lista caliente (Solus), NO repo entero (CachyOS)
Recompilar el catálogo entero por nivel multiplica la granja por N para ganancias que en el 90%
de los paquetes son ruido. Solus lo midió hace una década: el beneficio se concentra en un
puñado de libs con kernels vectorizables. Lista caliente inicial (todas ya en el catálogo):
| candidata | por qué |
|---|---|
| `zlib`, `zstd`, `xz` | (de)compresión en cada operación del propio hammer |
| `mesa` | el caso con más ganancia medible en GUI/juegos (llvmpipe/ACO vectorizan) |
| códecs (ffmpeg-clase, cuando entren) | el caso clásico AVX2 |
| `pixman`, `cairo` | raster 2D — **frontera GUI: sólo en worker** (gotcha zig-skew) |
- **T5 — piloto medido (2026-07-17, laptop TigerLake, clang 3 variantes base/v3/v4,
corpus 200MB mixto fuente+binarios):**
- **zstd: v3/v4 NO pagan — CORTADO de la lista.** Bench interno (`-b3 -i7`, estable):
base 144.2 vs v4 146.5 MB/s comprimiendo, 702 vs 692 descomprimiendo = ruido. Causa
conocida: zstd ya hace dispatch BMI2/AVX2 en **runtime** ⇒ el `-march` del build casi no
toca los kernels calientes. (Un +24% aparente de v4 en la 1ª tanda desapareció con i7:
era calentamiento — lección para T6: sólo benchs con control de varianza.)
- **zlib (madler): INCONCLUSO en laptop con carga** (el mismo binario base osciló 112157
MB/s entre tandas). Pero la pregunta estaba mal hecha: la respuesta buena no es una
variante v3 de zlib sino **zlib-ng** (fork con SIMD y dispatch en runtime, drop-in ABI;
Fedora 40 ya lo adoptó como zlib del sistema) ⇒ un solo artefacto, cero maquinaria de
variantes. Candidato a receta; medir zlib-ng vs zlib en máquina quieta cuando toque.
- **Consecuencia estructural**: los compresores modernos ya resuelven esto con dispatch
interno ⇒ la lista caliente de variantes se concentra donde NO hay dispatch: **mesa**
(llvmpipe/ACO compilan según -march) y códecs sin runtime-detect. El mecanismo T1T4
sigue válido pero su primer cliente real es mesa, no la dupla zlib/zstd.
- **T6 — regla de admisión**: una lib entra a la lista caliente con benchmark que lo
justifique, no por vibras. La lista vive en el repo junto a los números.
## 4. Lo que NO se hace
- **Repo entero v3/v4** — coste de granja ×N por ganancia marginal fuera de la lista caliente.
- **Dispatch en runtime / hwcaps** — no existe en musl y el modelo por-máquina es mejor para
hammer de todos modos (§1).
- **`-mcpu=native` jamás** — ni en variantes: el nivel nombrado es el contrato reproducible.
- **LTO generalizado como campaña** — hammer ya linkea estático con zig; el LTO por-receta se
evalúa donde el benchmark de T5/T6 lo pida, no como política global.
## Orden propuesto
1. **T5** (piloto zlib/zstd en worker, benchmark) — decide si el frente vale antes de tocar
framework.
2. **T1T2** (campo variant + mcpu pineado) — sólo si T5 da ganancia medible.
3. **T3T4** (índice + sonda en hydrate) — cierra el ciclo usable.
4. **mesa v3** — el premio gordo para juegos; coordinar con el plan de jaula de juegos
(`plan-jaula-juegos.md`), siempre en worker.