Skip to content

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 a np.sum a lo largo de tamaños 2^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:

  1. cProfile: corre python -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 en profiler_tour.md.
  2. line_profiler: añade el decorador @profile a tu función de bucle; corre kernprof -l -v bench_sum.py. Anota qué línea domina. Captura.
  3. memory_profiler: decora la función que reserva el array grande; corre python -m memory_profiler bench_sum.py. Anota el pico de delta de memoria. Captura.
  4. py-spy: en un terminal, corre python 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:

  1. results.json cubre los 21 tamaños.
  2. speedup.png muestra el cruce y la meseta claramente.
  3. 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.
  4. README.md responde a las cuatro preguntas de interpretación.

Escollos

  • for x in arr reserva un float de Python por iteración. Por eso es lento. La suma interna de NumPy no reserva.
  • np.sum para 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 top para verificar CPU monoproceso.
  • Permisos de py-spy. Puede necesitar sudo en Fedora. Si se queja, ver Fase 6 plan §7 pregunta abierta (d).
  • El decorador line_profiler debe estar presente en tiempo de ejecución. Si decoras con @profile fuera de kernprof, Python no sabe qué es @profile. Usa un decorador condicional (from line_profiler import profile funciona en versiones recientes) o corre solo vía kernprof.

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.