Optimizar sin medir suele atacar el código equivocado. cProfile, incluido en Python, registra llamadas y tiempos para mostrar dónde una carga consume recursos.

Definir una pregunta reproducible

El perfil debe responder algo concreto, por ejemplo “¿por qué importar 10 000 filas supera dos segundos?”. Registra versión de Python, dependencias, entrada, máquina y comando. Aparta trabajo ajeno y ejecuta varias veces. Una entrada artificial pequeña puede exagerar el coste de inicio.

python -m cProfile -o perfil.prof app.py
python -m pstats perfil.prof

En pstats:

sort cumulative
stats 20

tottime mide el cuerpo de una función sin subllamadas. cumtime incluye las funciones llamadas y revela caminos costosos.

ncalls puede mostrar llamadas primitivas y totales para recursión. percall divide tiempo por llamadas. Una función barata puede dominar al ejecutarse millones de veces; otra lenta quizá espera un servicio externo.

import cProfile
import pstats

with cProfile.Profile() as profiler:
    ejecutar_carga()

pstats.Stats(profiler).sort_stats("cumulative").print_stats(20)

Filtrar e interpretar

stats = pstats.Stats("perfil.prof")
stats.strip_dirs()
stats.sort_stats("cumulative")
stats.print_stats("miapp/", 25)
stats.print_callers(10)
stats.print_callees(10)

print_stats() acepta filtros de texto y cantidad. print_callers() muestra quién invoca una función costosa y print_callees() dónde delega. Ordenar por tottime descubre cuerpos caros; cumulative revela rutas costosas. Examina ambos. strip_dirs() facilita la lectura, aunque puede fusionar nombres iguales.

Guarda archivos separados para ejecuciones repetidas. Solo suma perfiles de cargas comparables. Para antes y después, conserva entrada y entorno, usa también un benchmark sin profiler e informa variabilidad, no solo la mejor ejecución.

Medir una fase concreta

python -m cProfile -o importacion.prof -s cumulative -m miapp.importador muestra.csv

El uso programático deja la preparación fuera:

registros = cargar_fixture("muestra.json")

profiler = cProfile.Profile()
profiler.enable()
resultado = transformar(registros)
profiler.disable()
profiler.dump_stats("transformacion.prof")

Calienta cachés solo si producción suele encontrarlas calientes. Si el arranque en frío importa, mídelo aparte. No imprimas resultados grandes dentro de la región porque el terminal puede dominar el perfil.

Distinguir CPU de espera

cProfile es determinista: observa eventos de llamada Python y atribuye tiempo transcurrido. Añade overhead, especialmente con muchas llamadas pequeñas. Úsalo para localizar candidatos y valida la mejora mediante benchmark sin profiler.

El tiempo en un cliente de base o HTTP puede ser espera, no CPU. Optimiza consulta, lotes, protocolo o concurrencia en lugar del pequeño wrapper. Las extensiones nativas pueden aparecer como pocas llamadas aunque realicen mucho trabajo. Para líneas, memoria, tareas asíncronas o muestreo en producción, elige una herramienta específica.

Seguir un ciclo disciplinado

Empieza por la mayor entrada relevante que puedas cambiar con seguridad. Formula una hipótesis, realiza un cambio enfocado, ejecuta pruebas, benchmark y perfil de nuevo. Ejemplos: sustituir búsquedas lineales repetidas por un conjunto, sacar parsing invariable del bucle, agrupar consultas o eliminar serialización redundante.

def ids_unicos_lento(filas):
    resultado = []
    for fila in filas:
        if fila["id"] not in resultado:
            resultado.append(fila["id"])
    return resultado


def ids_unicos(filas):
    return list(dict.fromkeys(fila["id"] for fila in filas))

La segunda versión mejora las búsquedas y conserva el primer orden, pero una medición representativa debe confirmar que esa función importa. Menos llamadas no siempre significa más rapidez, y la memoización intercambia memoria y actualidad por tiempo.

Proteger los datos

Los perfiles revelan rutas, módulos, arquitectura y nombres de funciones. Mantenlos fuera de directorios públicos y no confirmes capturas improvisadas de producción. Usa entradas saneadas y almacenamiento con acceso controlado.

Usa datos próximos al uso real y separa CPU de esperas de red o base de datos. Optimiza el mayor cuello seguro, ejecuta pruebas y mide otra vez. Consulta la guía de optimización Python.

Las comprobaciones automáticas pueden detectar regresiones grandes, pero límites amplios son mejores que aserciones frágiles de tiempo. Sigue una distribución de benchmarks en máquinas comparables e investiga con perfil cuando cambie.

Evitar errores de interpretación

No optimices la primera fila solo porque aparece arriba; confirma que ese tiempo afecta al objetivo del usuario. Los imports pueden dominar un comando breve y desaparecer en un servicio persistente. Fixtures y preparación pueden dominar si quedan dentro de la región medida. En recursión, lee con cuidado llamadas primitivas y totales.

La mejora de reloj real es el criterio final para latencia. CPU, memoria, throughput y cola de latencia pueden cambiar en sentidos distintos, así que declara qué métrica mejoras. Un cambio que acelera la mediana y empeora las solicitudes más lentas puede ser una regresión para una API.

Mantén un registro corto con comando, checksum de entrada, entorno, baseline, cambio, resultado y decisión. Así una optimización poco evidente conserva su justificación y los experimentos descartados siguen aportando información.

Perfilar sistemas concurrentes

En una aplicación concurrente, aísla una solicitud o unidad de trabajo si es posible. Un perfil agregado puede mezclar rutas rápidas y lentas y ocultar la causa. En aplicaciones asíncronas, el tiempo atribuido a una coroutine puede incluir periodos en los que cedió el control, según el punto observado. Relaciona el perfil con trazas, métricas de base de datos y logs de duración antes de concluir que existe consumo de CPU.

Los hilos también requieren contexto. El perfil creado en un hilo no necesariamente captura trabajo ejecutado por todos los demás. Diseña una carga controlada, identifica qué hilo realiza la operación y verifica el alcance de la herramienta en la versión usada. En multiproceso, cada proceso produce sus propios datos; conserva archivos separados y compáralos solo cuando representen unidades equivalentes.

Convertir datos en una decisión

Los resultados orientan prioridades, pero no sustituyen el juicio. Considera frecuencia, impacto para usuarios, riesgo del cambio y mantenimiento. Un fragmento que usa 40% de una rutina mensual puede importar menos que una función menor ejecutada en cada solicitud.

Describe la hipótesis antes de editar. Si el perfil señala conversiones repetidas, por ejemplo, mide una solución que convierta una vez y reutilice el resultado. Ejecuta pruebas funcionales con casos límite, porque una versión rápida que cambia orden, precisión o manejo de errores no es equivalente.

Tras confirmar la mejora, conserva un benchmark pequeño y documentado. Define una tolerancia que contemple ruido del entorno y observa tendencias, no una cifra aislada. Si el beneficio desaparece en otra máquina o entrada, registra esa limitación. El objetivo es una mejora demostrable en el escenario importante, no producir una tabla de perfil visualmente más corta.

La documentación oficial de profilers Python, consultada el 22 de julio de 2026, recomienda cProfile para la mayoría de usuarios por su menor overhead. Conserva carga y resultados para reproducir la decisión.