English · Español
Lab 03 — Presupuesto de vectorización¶
Objetivo: medir, en tu propia máquina, la ratio de aceleración bucle-Python-frente-a-NumPy en función del tamaño del array. Interiorizar la regla de 100×. Tocar cada uno de los cuatro profilers al menos una vez.
Tiempo estimado: 60–90 minutos.
Prerrequisitos: labs 00, 01.
Lo que produces¶
Un directorio experiments/06-vectorization-budget/ que contiene:
bench_sum.py— suma con bucle Python frente anp.suma lo largo de tamaños2^k,k ∈ {4..24}(21 tamaños).results.json—{size, op, mean_time_ns, std_time_ns, n_repeats}por medición.speedup.png— dos gráficos: (a) tiempo absoluto de ambas ops frente a tamaño, log-log; (b) ratio de aceleración frente a tamaño, log-x linear-y.profiler_tour.md— notas cortas de correr cada uno de los cuatro profilers sobre el mismo script.manifest.json.README.md— interpretación: ¿dónde ocurre el cruce en tu máquina? ¿Cuál es la ratio de meseta? ¿Por qué satura la ratio?
TODOs¶
Bloque A — benchmark¶
- Genera los tamaños:
[2**k for k in range(4, 25)]→ 16 .. 16777216 (~16M). - Para cada tamaño
N: - Reserva
arr = rng.standard_normal(N, dtype=np.float32). - Warm up ambas ops una vez (no cronometres).
- Cronometra
total = 0.0; for x in arr: total += x(bucle Python). Repite las veces suficientes para que una repetición sea ≥100 ms; registra media y desviación estándar. - Cronometra
total = arr.sum(). Mismo protocolo. - Para tamaños muy grandes (
N > 2^20), el bucle Python es lento; limita las repeticiones a 1. - Guarda en
results.json.
Bloque B — gráfico¶
- Gráfico (a): tiempo absoluto por llamada, log-log. Dos líneas.
- Gráfico (b): ratio de aceleración
t_python / t_numpy, log-x linear-y. Una línea. - Anota el punto de cruce (donde la ratio ≈ 1) con una línea vertical discontinua.
- Anota la ratio de meseta con una línea horizontal discontinua.
- Guarda como
speedup.png.
Bloque C — recorrido por los profilers¶
Elige el mayor tamaño donde el bucle Python aún corra en menos de 30 segundos (probablemente 2^22 ≈ 4M). Luego:
cProfile: correpython -m cProfile -s cumtime bench_sum.py(con el tamaño hardcodeado a tu N elegido). Anota las 5 funciones top por cumtime. Captura la salida relevante enprofiler_tour.md.line_profiler: añade el decorador@profilea tu función de bucle; correkernprof -l -v bench_sum.py. Anota qué línea domina. Captura.memory_profiler: decora la función que reserva el array grande; correpython -m memory_profiler bench_sum.py. Anota el pico de delta de memoria. Captura.py-spy: en un terminal, correpython bench_sum.py. En otro,py-spy record -o profile.svg -- python bench_sum.py. Captura el nombre del archivo del flamegraph + una descripción de una frase de qué domina.
No necesitas interpretar estos en profundidad; solo demuestra que has tocado cada uno. El objetivo es "la herramienta existe, el comando funcionó, aquí está el archivo".
Bloque D — interpretación¶
En README.md:
- ¿Dónde está el cruce (tamaño donde el bucle Python y NumPy tardan lo mismo)? La teoría predice ~256. ¿Qué mides tú?
- ¿Cuál es la ratio de meseta en el tamaño mayor? La teoría predice ~50–100×. ¿Qué mides tú?
- ¿Por qué satura la ratio? Pista: en tamaños grandes, tanto Python como NumPy están acotados por algo; ¿por qué?
- ¿Qué te dice la salida de cProfile que line_profiler no? Y viceversa.
Tres frases cada una. No rellenes.
Restricciones¶
- CPU governor
performance. Re-ajustar si tu máquina ha estado inactiva. - fp32 en todas partes.
- Un solo hilo. No corras otras cargas durante la medición.
- Misma semilla de RNG en todos los tamaños.
Resultados esperados¶
| N | Suma Python | NumPy .sum() |
Ratio |
|---|---|---|---|
| 16 | ~1 μs | ~1.5 μs | 0.7× |
| 256 | ~18 μs | ~1.6 μs | 11× |
| 4096 | ~280 μs | ~5 μs | 56× |
| 65536 | ~4.5 ms | ~70 μs | 64× |
| 1048576 | ~75 ms | ~1.0 ms | 75× |
| 16777216 | ~1.2 s | ~16 ms | 75× |
El cruce está entre N=16 y N=256. La meseta se asienta alrededor de 50–100×. Tus números estarán dentro del ±50% de estos en el i5-8250U.
Condiciones de parada¶
Hecho cuando:
results.jsoncubre los 21 tamaños.speedup.pngmuestra el cruce y la meseta claramente.- Cada uno de los cuatro profilers tiene al menos un artefacto (fragmento de salida, archivo SVG o snapshot de texto) referenciado desde
profiler_tour.md. README.mdresponde a las cuatro preguntas de interpretación.
Escollos¶
for x in arrreserva unfloatde Python por iteración. Por eso es lento. La suma interna de NumPy no reserva.np.sumpara arrays diminutos es más lento que Python. ~1.5 μs de sobrecoste de dispatch. No te sorprendas en N pequeño.- Pausas de GC durante el bucle Python. Para bucles de 1.2 s, el GC podría dispararse a mitad de medición.
gc.disable()defensivamente. - Procesos en background. Pestañas del navegador, etc. Ciérralas. Corre
toppara verificar CPU monoproceso. - Permisos de
py-spy. Puede necesitarsudoen Fedora. Si se queja, ver Fase 6 plan §7 pregunta abierta (d). - El decorador
line_profilerdebe estar presente en tiempo de ejecución. Si decoras con@profilefuera dekernprof, Python no sabe qué es@profile. Usa un decorador condicional (from line_profiler import profilefunciona en versiones recientes) o corre solo víakernprof.
Cuándo consultar solutions/¶
Después de que existan tus tres scripts y cuatro artefactos de profiler, y README.md esté completo. solutions/03-vectorization-budget-ref.md (en la apertura de fase) muestra el gráfico de referencia y las interpretaciones de los profilers.
Fin de los labs de la Fase 6. Siguiente: escribir PHASE_06_REPORT.md y learners/borja/phase-06/reflections.md.