SDD 25 §9.5: el iterador BPF, medido — y H7 pasa a tener precio
Cae el quinto de los seis experimentos nombrados. Un `iter/task` que emite registros binarios saca la foto de procesos cerca de 7x mas barato que el barrido de /proc (6,9-8,0x en cuatro corridas de 33 rondas y las dos direcciones), pero el numero que decide es otro: cuesta 1,75x el piso de `readdir` contra 12,1x de /proc. Leer los campos deja de tener precio; lo que se paga es enumerar. Dos barreras comprobadas, no supuestas: el programa se ata por id de tipo BTF del vmlinux (attach_btf_id=80137, prog_type=26) — sin DEBUG_INFO_BTF no hay a que atarse, no hay version de esto que esquive H7 —, y hace falta CAP_BPF+CAP_PERFMON, o sea que el consumidor es el supervisor, no la Card. Y los dos caminos NO dicen lo mismo: 24 de 245 comm difieren, todos kworkers, porque /proc SINTETIZA el nombre pegandole la workqueue. Un reemplazo vendido como «la misma informacion mas rapido» tenia una diferencia de contenido, y solo aparecio cotejando registro contra registro. H7 deja de decir «pahole en el lab y un re-hasheo». Son cuatro cosas: DEBUG_INFO_NONE=y en los .config sellados (BTF exige DWARF antes), el kernel no declara la receta dwarves aunque este sellada, ese pahole es DINAMICO pese a link="static" (INTERP + 5 NEEDED) y corre dentro del sandbox del build, y BTF mete la version de pahole en la bit-repro con el lab fuera de hash_inputs. Ya hay dos consumidores nombrados, no cero: este iterador y sched-ext. Lo que sigue SIN medir es el precio en bytes del bzImage, que es como se pago H1 — se mide construyendo, y esa es la primera tarea el dia que se decida. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
This commit is contained in:
+103
-9
@@ -36,6 +36,10 @@ El espejo de este documento para el lado tawasuyu es
|
||||
cada 100 ms vio **cero**; el proc connector, los 200. Y **el «copiar en O(1)» existe pero no en
|
||||
ext4**: en xfs/btrfs un reflink de 512 MiB tarda 0,020 ms contra 512,7 ms de `read+write`
|
||||
(§9.2).
|
||||
5. **Cuando `/proc` sí se sondea entero, hay algo cerca de 7× más barato** (§9.5, medido
|
||||
2026-08-31): un iterador BPF `task` saca la misma foto **al precio de listar el directorio**
|
||||
—1,75× el piso de `readdir` contra 12,1× de `/proc`—. Su portón es `DEBUG_INFO_BTF`, que sigue
|
||||
apagado, y H7 ahora dice **cuánto cuesta encenderlo** en vez de sólo que está apagado.
|
||||
|
||||
## 1. Método y condiciones — leer antes de citar un número
|
||||
|
||||
@@ -101,7 +105,9 @@ Leer el CPU propio por `/proc` cuesta **11× más** que `getrusage`. Y la mitad
|
||||
abrir la ruta, no el texto (1 271 vs 2 692 con el fd cacheado).
|
||||
|
||||
**Barrido completo** (291 procesos vivos): `readdir` solo **106 µs**; `readdir` + `stat` de
|
||||
cada pid **991 µs**, o sea **3,4 µs por proceso**.
|
||||
cada pid **991 µs**, o sea **3,4 µs por proceso**. Ese barrido tiene alternativa medida: un
|
||||
iterador BPF hace el mismo trabajo por **0,80 µs por proceso** contra 5,54 µs del mismo barrido
|
||||
medido en esa corrida (§9.5), pero exige BTF (H7).
|
||||
|
||||
### T4 · Contabilidad por unidad: cgroup v2 es O(1) donde `/proc` es O(procesos)
|
||||
|
||||
@@ -466,8 +472,39 @@ Su precio, concreto:
|
||||
reflink (§9) alimenta SDD 18 (wawafs), donde el store como FS vivo cambiaría el cociente.
|
||||
- **H6 · No `madvise(MADV_HUGEPAGE)` en el camino de build** mientras `defrag=madvise` y la
|
||||
granja corra apretada de RAM (T13).
|
||||
- **H7 · `DEBUG_INFO_BTF` sigue apagado** hasta que haya un consumidor real de BPF; cuando lo
|
||||
haya, la deuda es pahole en el lab y un re-hasheo, no un rediseño.
|
||||
- **H7 · `DEBUG_INFO_BTF` sigue apagado — pero ya no por falta de número.** §9.5 midió qué
|
||||
compra: un iterador `task` saca la foto de procesos **cerca de 7× más barato** que el barrido de
|
||||
`/proc` (6,9-8,0× en cuatro corridas y dos órdenes), y —lo que de verdad decide— **al precio de
|
||||
listar el directorio**: 1,75× el piso de `readdir`, contra 12,1× de `/proc`. Leer los campos deja de costar; sólo se paga enumerar.
|
||||
|
||||
El precio, en cambio, es más grande de lo que decía esta línea («pahole en el lab y un
|
||||
re-hasheo»). Son **cuatro** cosas, y ninguna es un rediseño:
|
||||
|
||||
1. **Los kernels no compilan con información de depuración**: `CONFIG_DEBUG_INFO_NONE=y` en los
|
||||
`.config` sellados. BTF exige DWARF, así que primero hay que encender `DEBUG_INFO_DWARF5`.
|
||||
Eso encarece el build, **no el `bzImage`**: el DWARF vive en `vmlinux`, que la receta no
|
||||
empaqueta — lo único que entra a la imagen es la sección `.BTF`.
|
||||
2. **pahole está en el catálogo, pero el kernel no lo declara.** `recipes/dwarves.toml` (1.30)
|
||||
se selló al cerrarse el frente libdw/musl, y su propia cabecera lo dice: «El kernel NO la
|
||||
declara todavía».
|
||||
3. **Ese pahole es DINÁMICO pese a `link = "static"`.** El binario del artefacto vigente
|
||||
(`8057bcd…`) trae `INTERP` y cinco `NEEDED`: `libelf`, `libz`, `liblzma`, `libbz2`, `libc`.
|
||||
Corre **dentro del sandbox del build del kernel**, así que esas librerías tienen que estar
|
||||
ahí o no arranca — y el fallo saldría como «pahole no está» estando.
|
||||
4. **BTF mete a pahole en el juego de la bit-repro.** El `.BTF` lo emite pahole ⇒ su versión
|
||||
pasa a ser parte de la identidad del artefacto, y el lab **no está en `hash_inputs`**: dos
|
||||
labs con pahole distinto sellarían bytes distintos en la misma dirección sin que el store lo
|
||||
note. Es el agujero conocido, con una herramienta más adentro.
|
||||
|
||||
**Y ya hay dos consumidores nombrados, no cero.** El iterador de §9.5, que H8 aceptaría porque
|
||||
trae ruta y símbolo (`bpf_iter_create`, `attach_btf_id` resuelto contra el BTF del vmlinux); y
|
||||
**sched-ext**, que es por lo que existe la receta de dwarves — `SCHED_CLASS_EXT` depende de
|
||||
`DEBUG_INFO_BTF`. El primero está medido; el segundo todavía no tiene su símbolo.
|
||||
|
||||
Lo que **no** está medido es el precio en bytes del `bzImage`, que es como se pagó H1. No se
|
||||
puede estimar del kernel de Artix: sus 6,4 MB de BTF son de un kernel modular enorme y no dicen
|
||||
nada de uno monolítico y pelado. **Se mide construyendo, y esa medición es el primer paso el día
|
||||
que se decida encenderlo** — no antes, porque enciende el re-hasheo de los cuatro kernels.
|
||||
- **H8 · El contrato de capacidades es parte del release del kernel** (§4.ter). Toda receta de
|
||||
kernel nueva declara su `[[target]]` con perfil; un kernel sin perfil sale **SIN COMPROBAR**, no
|
||||
aprobado. Y toda capacidad nueva entra con consumidor —ruta y símbolo— o no entra: «estaría bueno
|
||||
@@ -498,10 +535,10 @@ Detalle en el handoff espejo. En una línea cada una:
|
||||
la lista de símbolos garantizados (verificable con `hammer kernel probe`); tawasuyu elige
|
||||
backend en runtime.
|
||||
|
||||
## 9. Los experimentos nombrados — cuatro medidos, dos abiertos
|
||||
## 9. Los experimentos nombrados — cinco medidos, uno abierto
|
||||
|
||||
La primera corrida los dejó nombrados y sin medir, todos por privilegios o por herramienta
|
||||
ausente. **Cuatro cayeron el mismo día, con root.** Condiciones distintas a la corrida de arriba
|
||||
ausente. **Cuatro cayeron el mismo día, con root**, y el quinto —el iterador BPF— el 2026-08-31. Condiciones distintas a la corrida de arriba
|
||||
(load ~1,3 sobre 4 vCPU, 4,3 GiB libres) ⇒ los números de acá **no se cruzan** con los de §2.
|
||||
|
||||
### 9.1 · proc connector y `taskstats` — MEDIDO (`bench_connector.c`, `connector.txt`)
|
||||
@@ -588,11 +625,60 @@ Dos lecturas:
|
||||
`strace -c` le atribuye es espera **bloqueada**, no CPU — y por eso `strace -c` no sirve para
|
||||
comparar compiladores, sólo para contar llamadas.
|
||||
|
||||
### 9.5 · Lo que sigue abierto
|
||||
### 9.5 · Iterador BPF contra `/proc` — MEDIDO (`bench_bpf_iter.c` + `bpf_iter_task.bpf.c` → `bpf-iter.txt`)
|
||||
|
||||
1. **Iterador BPF contra `/proc`** (T3): exige BTF y privilegios. Va detrás de H7 —
|
||||
`DEBUG_INFO_BTF` está apagado en las cuatro recetas y su deuda (pahole) ya está medio pagada.
|
||||
2. **Un Intel con PTI**: todos los pisos de syscall de acá son de un AMD sin PTI. En el laptop
|
||||
Medido el **2026-08-31**, sobre el kernel de desarrollo (Artix 7.1.4, que sí trae BTF) —
|
||||
**no sobre un kernel de hammer, que es justamente lo que está en discusión**. Tercera corrida,
|
||||
condiciones propias: **sus números no se cruzan con los de §2 ni con los de §9.1-9.4.**
|
||||
|
||||
Los dos caminos hacen **el mismo trabajo**: sacar `{pid, ppid, estado, utime, stime, comm}` de
|
||||
todos los procesos vivos, que es la foto que sandokan necesita de la máquina. Uno lo hace con
|
||||
`readdir` + `open`+`read`+parse de `/proc/<pid>/stat`; el otro con un `iter/task` que escribe
|
||||
**registros binarios de tamaño fijo** con `bpf_seq_write`, leídos de un solo fd.
|
||||
|
||||
| 245 procesos vivos, 33 rondas, mínimo | total | por proceso | × sobre el piso |
|
||||
|---|---|---|---|
|
||||
| A) `/proc` readdir+`stat`+parse | 1 356,1 µs | 5,54 µs | 12,1× |
|
||||
| B) iterador BPF (binario) | **196,1 µs** | **0,80 µs** | **1,75×** |
|
||||
| C) piso: `readdir(/proc)` a secas | 112,1 µs | 0,46 µs | 1× |
|
||||
|
||||
**6,9× a favor del iterador** — pero el número que decide no es ése: **el iterador cuesta casi lo
|
||||
mismo que listar el directorio y no abrir nada** (1,75× el piso, contra 12,1× de `/proc`). Leer
|
||||
los campos deja de tener precio; lo que se paga es enumerar.
|
||||
|
||||
Siete corridas, dos órdenes (A→B y B→A, para descartar que el primero de cada ronda pague el
|
||||
enfriamiento de las dentries de `/proc`): **6,9-8,0× en las cuatro de 33 rondas**, en las dos
|
||||
direcciones. La de 15 rondas dio 4,50× y es de la que hay que desconfiar, no de la mayoría: con
|
||||
pocas rondas el mínimo no converge y la máquina estaba cargada.
|
||||
|
||||
**Amortización**: cargar y verificar el programa cuesta **13-17 ms una vez**; cada barrido ahorra
|
||||
más de 1 ms ⇒ **se paga solo en 12-40 barridos** según la corrida, 15 en la citada. Para un
|
||||
sandokan que mire la máquina una vez por segundo, medio minuto.
|
||||
|
||||
**Lo que la ganancia cuesta, comprobado y no supuesto** — son dos barreras, no una:
|
||||
|
||||
1. **BTF es un portón, no una optimización.** El programa cargado informa
|
||||
`prog_type=26 (BPF_PROG_TYPE_TRACING)`, `attach_btf_id=80137`, `attach_btf_obj_id=1`: el
|
||||
iterador **se ata por id de tipo BTF del vmlinux**. Un kernel sin `DEBUG_INFO_BTF` no publica
|
||||
`/sys/kernel/btf/vmlinux` y no hay a qué atarse. **No hay versión de esto que esquive H7.**
|
||||
2. **Privilegio**: `CAP_BPF`+`CAP_PERFMON` o root. Sin él, `bpf_object__load` sale
|
||||
`Operation not permitted`. Una Card no lo tiene ni debe tenerlo ⇒ **el consumidor es el
|
||||
supervisor, no la Card** — que es donde vive sandokan, así que encaja.
|
||||
|
||||
**Y los dos caminos NO dicen exactamente lo mismo.** De 245 procesos, **24 `comm` difieren**, y
|
||||
son **todos kworkers**: `/proc` **sintetiza** el nombre pegándole la workqueue que el hilo está
|
||||
sirviendo (`kworker/0:0H-kblockd`), mientras `task->comm` trae el nombre pelado
|
||||
(`kworker/0:0H`). Ningún proceso de usuario difiere. Para las Cards da igual; para cualquiera que
|
||||
muestre nombres de kworker, no. **Un reemplazo que se vende como "la misma información más
|
||||
rápido" tenía una diferencia de contenido, y sólo apareció cotejando registro contra registro** —
|
||||
no medir es lo que la habría dejado pasar.
|
||||
|
||||
**Nota de método**: el bench aborta con exit 2 si el iterador devuelve **cero registros**. Un
|
||||
barrido vacío es infinitamente rápido y no mide nada: es CLAUDE.md §3 aplicado a un benchmark.
|
||||
|
||||
### 9.6 · Lo que sigue abierto
|
||||
|
||||
1. **Un Intel con PTI**: todos los pisos de syscall de acá son de un AMD sin PTI. En el laptop
|
||||
(TigerLake) las mismas medidas darían peor, y ahí T3/T6 duelen más.
|
||||
|
||||
## 10. Evidencia
|
||||
@@ -618,6 +704,14 @@ Se compilan igual que los otros (`bench_sqpoll` pide `-luring`) y los tres prime
|
||||
`hammer kernel contract --list` (la vista). Los ficheros de esta carpeta no cambian; el contrato sí
|
||||
—cada vez que un consumidor nuevo usa el kernel por nombre.
|
||||
|
||||
**Tercera corrida (§9.5, iterador BPF, 2026-08-31)**: `bpf_iter_task.bpf.c` (el programa BPF) +
|
||||
`bench_bpf_iter.c` (el de usuario) → `bpf-iter.txt`. Se compilan con
|
||||
`clang -O2 -g -target bpf -D__TARGET_ARCH_x86 -c bpf_iter_task.bpf.c -o bpf_iter_task.bpf.o` y
|
||||
`cc -O2 -o bench_bpf_iter bench_bpf_iter.c -lbpf`; se corre `./bench_bpf_iter [rondas] [orden]`
|
||||
como root, con el `.bpf.o` al lado. **No usa `vmlinux.h`** —haría falta bpftool, que no está en la
|
||||
máquina ni en el store— sino los tipos mínimos declarados a mano con `preserve_access_index`, que
|
||||
es lo que deja a CO-RE reubicar los offsets contra el BTF del kernel en carga.
|
||||
|
||||
**El pago de H1** (2026-08-30): `contrato-h1-pagado.txt` — el barrido entero con los cuatro kernels
|
||||
reconstruidos (5 de 5 vigentes en verde, 10 superados listados) y el `diff-back` del plan de gioser
|
||||
contra su `.config` nuevo. Es la única salida de esta carpeta que sí cambia si se vuelve a correr:
|
||||
|
||||
Reference in New Issue
Block a user