#!/usr/bin/env python3 """Funde N weights.rs del MISMO pallet en uno que cobra el MÁXIMO de los N procesos. python3 scripts/bench/max-de-n.py salida.rs proceso1.rs proceso2.rs ... (N >= 2) Por qué: Laurha midió el 2026-10-24 en la Pi 5, dentro del santuario (núcleo aislado, governor performance, CI parada), que 8 procesos idénticos de frame-omni-bencher dan base weights que se separan hasta un 66 % (revoke_validator: 18,8 a 31,3 µs), mientras el mínimo apenas se mueve. Un weight cobra el caso malo, y el caso malo existe: se cobra el máximo de N >= 5 procesos, no la media ni la mediana. Qué hace: - Exige que los N ficheros tengan el MISMO esqueleto: todo el texto salvo los números del cuerpo, desde `pub trait WeightInfo`. Si un fichero trae otra lista de extrinsics, otro acceso a storage u otra plantilla, se niega. Mezclar pallets o plantillas no es un máximo. - `Weight::from_parts(a, b)`: toma el máximo de cada coeficiente. - `reads(n)` y `writes(n)`: deben ser IGUALES en los N (cuentan accesos, no tiempo). Si no lo son, se niega. - Los comentarios numéricos del cuerpo: «Medido» y «Estimado» toman el máximo; «Tiempo mínimo» toma el mínimo. - Cabecera: la del primer fichero, más una línea que declara N y el sha256 de cada entrada. """ import hashlib, re, sys NUM = re.compile(r"\d(?:[\d_]*\d)?") # «7_u64» → 7; nunca se come el guion bajo final INICIO_CUERPO = "pub trait WeightInfo" def partir(texto): i = texto.index(INICIO_CUERPO) return texto[:i], texto[i:] def esqueleto(cuerpo): return NUM.sub("#", cuerpo) def numeros(cuerpo): return [(m.start(), m.end(), int(m.group().replace("_", ""))) for m in NUM.finditer(cuerpo)] def contexto(cuerpo, inicio): """La línea donde cae un número, para decidir la regla.""" a = cuerpo.rfind("\n", 0, inicio) + 1 b = cuerpo.find("\n", inicio) return cuerpo[a:b if b != -1 else len(cuerpo)] def con_guiones(n): s = str(n) partes = [] while s: partes.insert(0, s[-3:]) s = s[:-3] return "_".join(partes) def main(): if len(sys.argv) < 4: sys.exit(__doc__) salida, entradas = sys.argv[1], sys.argv[2:] if len(entradas) < 5: print(f"AVISO: N = {len(entradas)} < 5. La regla de la casa (Laurha, 24-oct) cobra el máximo de N >= 5 procesos.", file=sys.stderr) textos = [open(p).read() for p in entradas] cabecera, cuerpo0 = partir(textos[0]) cuerpos = [partir(t)[1] for t in textos] sk = esqueleto(cuerpo0) for p, c in zip(entradas, cuerpos): if esqueleto(c) != sk: sys.exit(f"RECHAZO: {p} no tiene el mismo esqueleto que {entradas[0]} (otra lista de extrinsics, storage o plantilla)") listas = [numeros(c) for c in cuerpos] out, cursor = [], 0 for k, (ini, fin, _) in enumerate(listas[0]): valores = [lst[k][2] for lst in listas] linea = contexto(cuerpo0, ini) if "reads(" in linea or "writes(" in linea: if len(set(valores)) != 1: sys.exit(f"RECHAZO: los conteos de storage difieren entre procesos en «{linea.strip()}»: {valores}") v = valores[0] elif "minimo" in linea or "mínimo" in linea: v = min(valores) else: v = max(valores) crudo = cuerpo0[ini:fin] nuevo = con_guiones(v) if "_" in crudo else str(v) # conserva el formato del literal out.append(cuerpo0[cursor:ini]) out.append(nuevo) cursor = fin out.append(cuerpo0[cursor:]) shas = " ".join(hashlib.sha256(t.encode()).hexdigest()[:12] for t in textos) nota = (f"// MAXIMO DE N = {len(entradas)} PROCESOS (scripts/bench/max-de-n.py): cada coeficiente\n" f"// de tiempo y de proof es el maximo de los N; reads/writes iguales en los N.\n" f"// Entradas (sha256[:12]): {shas}\n\n") with open(salida, "w") as f: f.write(cabecera + nota + "".join(out)) print(f"{salida}: máximo de {len(entradas)} procesos, {len(listas[0])} números revisados") main()