{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Perfilado (Profiling) de rutinas de Python\n", "\n", "El primer paso antes de optimizar es identificar los sectores críticos o \"cuellos de botella\" de nuestro programa. A esto lo llamamos hacer un perfilado (*profiling*) del código." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ¿Qué es *profiling*?\n", "\n", "Se refiere a medir \n", "\n", "1. Tiempo de ejecución, ya sea total, por función o por línea de código.\n", "1. Utilización de recursos de hardware como memoria, cpu o disco duro.\n", "\n", "de una rutina con el fin de encontrar aquellas secciones más lentas e ineficientes (sectores críticos). A continuación veremos como hacer *profiling* de nuestro código en Python utilizando las magias de *IPython*." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A modo de ejemplo haremos *profiling* de un script de Python que calcula el famoso fractal conocido como el [Conjunto de Julia](https://es.wikipedia.org/wiki/Conjunto_de_Julia) (Julia Set), llamado así en honor a su descubridor, el matemático francés Gaston Maurice Julia.\n", "\n", "El fractal de Julia corresponde a todos los números complejos $z$ para los que la siguiente ecuación\n", "\n", "$$\n", "f(z) = z^2 + c,\n", "$$\n", "\n", "no diverge al ser calculada recursivamente. En la ecuación anterior $c \\in \\mathbb{C}$.\n", "\n", "El *script* [fractal.py](./fractal.py) tiene una implementación del conjunto de Julia usando Python puro, es decir sin usar librerias. La implementación se muestra a continuación:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "ExecuteTime": { "end_time": "2020-08-04T01:24:31.028879Z", "start_time": "2020-08-04T01:24:30.607751Z" } }, "outputs": [ { "data": { "text/html": [ "
def evaluate_z(zi, zr, maxiters=50, cr=-0.835, ci=-0.2321):\n",
" nit = 0\n",
" zi2 = zi**2\n",
" zr2 = zr**2\n",
" while zi2 + zr2 <= 4. and nit < maxiters:\n",
" zi = 2*zr*zi + ci\n",
" zr = zr2 - zi2 + cr\n",
" zr2 = zr**2\n",
" zi2 = zi**2 \n",
" nit +=1\n",
" return nit\n",
" \n",
"def make_fractal(N, maxiters=50):\n",
" image = []\n",
" for i in range(N):\n",
" row = []\n",
" for j in range(2*N):\n",
" zi = -1.0 + i*2/N\n",
" zr = -2.0 + j*2/N\n",
" row.append(evaluate_z(zi, zr, maxiters))\n",
" image.append(row)\n",
" return image\n",
"
`: Se usa una precisión de `
` dígitos en los resultados\n",
"- `-q`: No imprimir los resultados\n",
"- `-o`: Retorna un objeto `TimeitResult`, con el cual podemos manipular, analizar y graficar los tiempos de ejecución\n",
"\n",
"\n",
"Esta magia se utiliza como \n",
"\n",
"```python\n",
" %timeit -r10 -n5 mi_funcion(mis_argumentos)\n",
"```\n",
"\n",
"Que en pseudo código sería como\n",
"\n",
"```python\n",
"tiempos = []\n",
"for _ in range(10): # Repeticiones (\n",
"\n",
":::{note}\n",
"\n",
"La opción `-t` carga el gráfico en una pestaña de navegador nueva.\n",
"\n",
":::\n",
"\n",
"Esta herramienta puede mejorar considerablemente el estudio de nuestro código cuando se tiene una gran cantidad de funciones en distintas jerarquías."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**`cProfile` sin IPython**\n",
"\n",
"Si no contamos con el ambiente IPython podemos usar cProfile directamente sobre un script de Python con\n",
"\n",
" python -m cProfile -o tabla.prof script.py\n",
" \n",
"Donde el resultado queda grabado en el archivo tabla.prof"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Magia `%lprun`\n",
"\n",
"Hemos visto como medir el tiempo total de un código en Python y el tiempo desglosado por cada llamado a función de dicho código. Pero en ocasiones puede resultar más informativo estudiar el tiempo de ejecución de cada linea de nuestro código por separado.\n",
"\n",
":::{note}\n",
"\n",
"Esto corresponde a un tipo de *profiling* llamado *line by line*.\n",
"\n",
":::\n",
"\n",
"Podemos hacer este tipo de profiling usando la extensión externa [`line_profiler`](https://github.com/pyutils/line_profiler). \n",
"\n",
"Una vez instalada se debe cargar la extensión para habilitar la magia `lprun`, como se muestra a continuación:\n",
"\n",
"**Ejemplo**\n",
"\n",
"```python\n",
"%load_ext line_profiler\n",
"%lprun -f mi_método mi_rutina\n",
"```\n",
"\n",
"Esta magia requiere que se especifique un método o función dentro de la rutina con el argumento `-f`. Ejecutar la magia levantará una pestaña con una tabla.\n",
"\n",
"La tabla tiene una fila por linea de código y las siguientes columnas:\n",
"\n",
"- Line: Número de la linea dentro del código fuente\n",
"- Hits: La cantidad de veces que se llama a esa linea\n",
"- Time: Tiempo total de dicha linea\n",
"- Per hit: Tiempo total dividido la cantidad de llamadas\n",
"- %Time: Porcentaje del tiempo de dicha linea con respecto al tiempo total del método\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Medir uso de memoria"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Magia `%memit`\n",
"\n",
"Podemos medir la memoria utilizada por un proceso usando la librería externa [`memory_profiler`](https://github.com/pythonprofilers/memory_profiler). Se puede instalar facilmente con:\n",
"\n",
" conda install memory_profiler\n",
"\n",
"Luego se habilita y utiliza con\n",
"\n",
"```python\n",
"%load_ext memory_profiler\n",
"%memit funcion()\n",
"```\n",
"\n",
"Que retorna la cantidad de memoria RAM usada por `funcion`\n",
"\n",
":::{note}\n",
"\n",
"Con esto también se habilita la magia `%mprun` para medir el uso de memoria linea a linea\n",
"\n",
":::"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Módulo `tracemalloc`\n",
"\n",
"Python tiene un módulo nativo llamado [`tracemalloc`](https://docs.python.org/3/library/tracemalloc.html) que permite calcular estadísticas sobre la memoria reservada por un proceso o script de Python\n",
"\n",
"A continuación se muestra un ejemplo de uso\n",
"\n",
"```python\n",
"import tracemalloc\n",
"\n",
"tracemalloc.start()\n",
"\n",
"# Acá se corre el código que quieres estudiar\n",
"\n",
"snapshot = tracemalloc.take_snapshot()\n",
"top_stats = snapshot.statistics('lineno')\n",
"\n",
"```\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.16"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {
"height": "calc(100% - 180px)",
"left": "10px",
"top": "150px",
"width": "294.4px"
},
"toc_section_display": true,
"toc_window_display": true
}
},
"nbformat": 4,
"nbformat_minor": 2
}