Notas para contenedor de docker:

Comando de docker para ejecución de la nota de forma local:

nota: cambiar <ruta a mi directorio> por la ruta de directorio que se desea mapear a /datos dentro del contenedor de docker.

docker run --rm -v <ruta a mi directorio>:/datos --name jupyterlab_numerical -p 8888:8888 -d palmoreck/jupyterlab_numerical:1.1.0

password para jupyterlab: qwerty

Detener el contenedor de docker:

docker stop jupyterlab_numerical

Documentación de la imagen de docker palmoreck/jupyterlab_numerical:1.1.0 en liga.


Esta nota utiliza métodos vistos en 1.5.Integracion_numerica

Instalamos las herramientas que nos ayudarán al perfilamiento:


In [1]:
%pip install -q --user line_profiler


  WARNING: The script kernprof is installed in '/home/miuser/.local/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
WARNING: You are using pip version 19.3.1; however, version 20.0.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
Note: you may need to restart the kernel to use updated packages.

In [2]:
%pip install -q --user memory_profiler


  WARNING: The script mprof is installed in '/home/miuser/.local/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
WARNING: You are using pip version 19.3.1; however, version 20.0.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
Note: you may need to restart the kernel to use updated packages.

In [3]:
%pip install -q --user psutil


WARNING: You are using pip version 19.3.1; however, version 20.0.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
Note: you may need to restart the kernel to use updated packages.

In [4]:
%pip install -q --user guppy3


WARNING: You are using pip version 19.3.1; however, version 20.0.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
Note: you may need to restart the kernel to use updated packages.

La siguiente celda reiniciará el kernel de IPython para cargar los paquetes instalados en la celda anterior. Dar Ok en el mensaje que salga y continuar con el contenido del notebook.


In [5]:
import IPython
app = IPython.Application.instance()
app.kernel.do_shutdown(True)


Out[5]:
{'status': 'ok', 'restart': True}

In [1]:
import math
from scipy.integrate import quad

Perfilamiento en Python

En esta nota revisamos algunas herramientas de Python para perfilamiento de código: uso de cpu y memoria.

Medición de tiempos con:

  • Módulo time de Python.

  • %time de comandos de magic <- esta herramienta es sólo para medir tiempos de un statement y sólo la coloco para referencia pero no se usará en la nota.

  • /usr/bin/time) de Unix.

  • %timeit de comandos de magic.

Perfilamiento:

Medición de tiempos

El primer acercamiento que usamos en la nota para perfilar nuestro código es identificar qué es lento, otras mediciones son la cantidad de RAM, el I/O en disco o network.

1) Uso de time


In [2]:
import time

Regla compuesta del rectángulo

Ejemplo de implementación de regla compuesta de rectángulo: usando math

Utilizar la regla compuesta del rectángulo para aproximar la integral $\int_0^1e^{-x^2}dx$ con $10^6$ subintervalos.


In [3]:
f=lambda x: math.exp(-x**2) #using math library

In [4]:
def Rcf(f,a,b,n): #Rcf: rectángulo compuesto para f
    """
    Compute numerical approximation using rectangle or mid-point method in 
    an interval.
    Nodes are generated via formula: x_i = a+(i+1/2)h_hat for i=0,1,...,n-1 and h_hat=(b-a)/n
    Args:
        f (lambda expression): lambda expression of integrand
        a (int): left point of interval
        b (int): right point of interval
        n (int): number of subintervals
    Returns:
        Rcf (float) 
    """
    h_hat=(b-a)/n
    nodes=[a+(i+1/2)*h_hat for i in range(0,n)]
    sum_res=0
    for node in nodes:
        sum_res=sum_res+f(node)
    return h_hat*sum_res

In [5]:
n=10**6

In [6]:
start_time = time.time()
aprox=Rcf(f,0,1,n)
end_time = time.time()

In [7]:
secs = end_time-start_time
print("Rcf tomó",secs,"segundos" )


Rcf tomó 0.3661477565765381 segundos

Obs: recuérdese que hay que evaluar que se esté resolviendo correctamente el problema. En este caso el error relativo nos ayuda


In [8]:
def err_relativo(aprox, obj):
    return math.fabs(aprox-obj)/math.fabs(obj) #obsérvese el uso de la librería math

In [9]:
obj, err = quad(f, 0, 1)
err_relativo(aprox,obj)


Out[9]:
6.71939731300312e-14

Comentarios:

  • Tómese en cuenta que al medir tiempos de ejecución, siempre hay variación en la medición. Tal variación es normal.

  • Considérese que la máquina en la que se están corriendo las pruebas puede estar realizando otras tareas mientras se ejecuta el código, por ejemplo acceso a la red, al disco o a la RAM. Por ello, son factores que pueden causar variación en el tiempo de ejecución del programa.

  • Si se van a realizar reportes de tiempos, es importante indicar las características de la máquina en la que se están haciendo las pruebas, p.ej: Dell E6420 con un procesador Intel Core I7-2720QM (2.20 GHz, 6 MB cache, Quad Core) y 8 GB de RAM en un Ubuntu $13.10$.

2) Uso de /usr/bin/time de Unix

Para la línea de comando /usr/bin/time primero escribimos el siguiente archivo en la ruta donde se encuentra este notebook con la línea de comando magic %file


In [10]:
%%file Rcf.py
import math
def Rcf(f,a,b,n): #Rcf: rectángulo compuesto para f
    """
    Compute numerical approximation using rectangle or mid-point method in 
    an interval.
    Nodes are generated via formula: x_i = a+(i+1/2)h_hat for i=0,1,...,n-1 and h_hat=(b-a)/n
    Args:
        f (lambda expression): lambda expression of integrand
        a (int): left point of interval
        b (int): right point of interval
        n (int): number of subintervals
    Returns:
        Rcf (float) 
    """
    h_hat=(b-a)/n
    nodes=[a+(i+1/2)*h_hat for i in range(0,n)]
    sum_res=0
    for node in nodes:
        sum_res=sum_res+f(node)
    return h_hat*sum_res
if __name__=="__main__": #añadimos este bloque para ejecución de la función Rcf
        n=10**6
        f=lambda x: math.exp(-x**2)
        print("aproximación: {:0.6e}".format(Rcf(f,0,1,n)))


Writing Rcf.py

Ver liga y liga2 para saber qué es lo que hace la línea if __name__ == "__main__":

Lo siguiente es necesario si no tienen instalado el comando /usr/bin/time:


In [11]:
%%bash
sudo apt-get install time


Reading package lists...
Building dependency tree...
Reading state information...
The following NEW packages will be installed:
  time
0 upgraded, 1 newly installed, 0 to remove and 24 not upgraded.
Need to get 26.2 kB of archives.
After this operation, 79.9 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu bionic/main amd64 time amd64 1.7-25.1build1 [26.2 kB]
Fetched 26.2 kB in 4s (6280 B/s)
Selecting previously unselected package time.
(Reading database ... 18521 files and directories currently installed.)
Preparing to unpack .../time_1.7-25.1build1_amd64.deb ...
Unpacking time (1.7-25.1build1) ...
Setting up time (1.7-25.1build1) ...
debconf: delaying package configuration, since apt-utils is not installed

In [12]:
%%bash
/usr/bin/time -p python3 Rcf.py #la p es de portabilidad, 
                                #ver: http://manpages.ubuntu.com/manpages/xenial/man1/time.1.html
                                #para mayor información


aproximación: 7.468241e-01
real 0.46
user 0.42
sys 0.03

Comentarios:

  • real que mide el wall clock o elapsed time.
  • user que mide la cantidad de tiempo de tu ejecución que la CPU gastó para funciones que no están relacionadas con el kernel* del sistema.
  • sys que mide la cantidad de tiempo de tu ejecución que la CPU gastó en funciones a nivel de kernel del sistema.

*Ver kernel operating system) para definición del kernel de una máquina.

Obs: Una función relacionada con el kernel del sistema es el alojamiento de memoria al crear una variable. Otras son las instrucciones relacionadas con el I/O como leer de la memoria, disco o network.

  • La ventaja de /usr/bin/time es que no es específico de Python.
  • Este comando incluye el tiempo que le toma al sistema iniciar el ejecutable de python (que puede ser significativo si se inician muchos procesos vs un sólo proceso). En el caso de tener short-running scripts donde el tiempo de inicio es significativo del tiempo total entonces /usr/bin/time puede ser una medida útil.

Nota: Si se suma user con sys se tiene una idea de cuánto tiempo se gastó en la CPU y la diferencia entre este resultado y real da una idea de cuánto tiempo se gastó para I/O o también puede dar una idea de la cantidad de tiempo que se ocupó el sistema en correr otras tareas.

  • Se puede utilizar la flag verbose para obtener más información:

In [13]:
%%bash
/usr/bin/time --verbose python3 Rcf.py


aproximación: 7.468241e-01
	Command being timed: "python3 Rcf.py"
	User time (seconds): 0.37
	System time (seconds): 0.04
	Percent of CPU this job got: 89%
	Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.45
	Average shared text size (kbytes): 0
	Average unshared data size (kbytes): 0
	Average stack size (kbytes): 0
	Average total size (kbytes): 0
	Maximum resident set size (kbytes): 41112
	Average resident set size (kbytes): 0
	Major (requiring I/O) page faults: 0
	Minor (reclaiming a frame) page faults: 9078
	Voluntary context switches: 26
	Involuntary context switches: 581
	Swaps: 0
	File system inputs: 16
	File system outputs: 0
	Socket messages sent: 0
	Socket messages received: 0
	Signals delivered: 0
	Page size (bytes): 4096
	Exit status: 0

y una explicación (breve) del output se puede encontrar aquí. Para el caso de Major (requiring I/O) nos interesa que sea $0$ pues indica que el sistema operativo tiene que cargar páginas de datos del disco pues tales datos ya no residen en RAM (por alguna razón).

3) Uso de %timeit

El módulo de timeit es otra forma de medir el tiempo de ejecución en la CPU.

Nota: el módulo de timeit desabilita temporalmente el garbage collector* de Python (esto es, no habrá desalojamiento en memoria de objetos de Python que no se utilicen). Si el garbage collector es invocado en tus operaciones para un ejemplo del mundo real, esto puede ser una razón de posibles diferencias que obtengas en las mediciones de tiempo.

*sugiero buscar qué es el garbage collector en blogs, por ejemplo: liga o liga2 o liga3.


In [14]:
%timeit?


Docstring:
Time execution of a Python statement or expression

Usage, in line mode:
  %timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] statement
or in cell mode:
  %%timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] setup_code
  code
  code...

Time execution of a Python statement or expression using the timeit
module.  This function can be used both as a line and cell magic:

- In line mode you can time a single-line statement (though multiple
  ones can be chained with using semicolons).

- In cell mode, the statement in the first line is used as setup code
  (executed but not timed) and the body of the cell is timed.  The cell
  body has access to any variables created in the setup code.

Options:
-n<N>: execute the given statement <N> times in a loop. If <N> is not
provided, <N> is determined so as to get sufficient accuracy.

-r<R>: number of repeats <R>, each consisting of <N> loops, and take the
best result.
Default: 7

-t: use time.time to measure the time, which is the default on Unix.
This function measures wall time.

-c: use time.clock to measure the time, which is the default on
Windows and measures wall time. On Unix, resource.getrusage is used
instead and returns the CPU user time.

-p<P>: use a precision of <P> digits to display the timing result.
Default: 3

-q: Quiet, do not print result.

-o: return a TimeitResult that can be stored in a variable to inspect
    the result in more details.

.. versionchanged:: 7.3
    User variables are no longer expanded,
    the magic line is always left unmodified.

Examples
--------
::

  In [1]: %timeit pass
  8.26 ns ± 0.12 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

  In [2]: u = None

  In [3]: %timeit u is None
  29.9 ns ± 0.643 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

  In [4]: %timeit -r 4 u == None

  In [5]: import time

  In [6]: %timeit -n1 time.sleep(2)


The times reported by %timeit will be slightly higher than those
reported by the timeit.py script when variables are accessed. This is
due to the fact that %timeit executes the statement in the namespace
of the shell, compared with timeit.py, which uses a single setup
statement to import function or create variables. Generally, the bias
does not matter as long as results from timeit.py are not mixed with
those from %timeit.
File:      /usr/local/lib/python3.6/dist-packages/IPython/core/magics/execution.py

In [15]:
%timeit -n 5 -r 10 Rcf(f,0,1,n)


333 ms ± 11.1 ms per loop (mean ± std. dev. of 10 runs, 5 loops each)

para este caso se está ejecutando la función Rcf en un loop de tamaño $5$, se están promediando los tiempos de las $5$ ejecuciones y calculando su desviación estándar y al repetir esto $10$ veces se está reportando el mejor resultado. $ms$ es milisecond, $\mu s$ es microsecond y $ns$ es nanosecond.

Comentarios:

  • timeit se recomienda usar para secciones de código pequeñas. Para secciones más grandes típicamente modificar el valor de $n$ (ejecutar el código n veces en un loop) resulta en mediciones distintas.

  • Ejecuta timeit varias ocasiones para asegurarse que se obtienen tiempos similares. Si observas una gran variación en las mediciones de tiempo entre distintas repeticiones de timeit, realiza más repeticiones hasta tener un resultado estable.

Medición de uso de CPU

1) Uso de cProfile

cProfile es una herramienta built-in en la standard library para perfilamiento. Se utiliza con la implementación CPython de Python (ver liga para explicación de implementaciones de Python) para medir el tiempo de ejecución de cada función en el programa.

Se ejecuta desde la línea de comandos o con un comando de magic. La flag -s indica que se ordene el resultado por el tiempo acumulado dentro de cada función.

El output siguiente de cProfile muestra:

  • El tiempo total de ejecución, el cual incluye el tiempo del bloque de código que estamos midiendo y el overhead al usar cProfile. Por esta razón se tiene un mayor tiempo de ejecución que con las mediciones de tiempo anteriores.

  • La columna ncalls que como el nombre indica, muestra el número de veces que se llamó a cada función. En este caso las funciones lambda y math.exp son las que se llaman un mayor número de veces: $n=10^6$ veces. La columnatottime muestra el tiempo que tardaron estas funciones en ejecutarse (sin llamar a otras funciones).

  • La columna percall es el cociente entre tottime y ncalls.

  • La columna cumtime contiene el tiempo gastado en la función y en las demás que llama. Por ejemplo la función Rcf llama a listcomp por lo que es natural que Rcf esté más arriba en el output ordenado de cProfile. Esto también ocurre con lambda y math.exp pues la primera llama a la segunda.

  • La columna de percall es un cociente entre la columna cumtime y el llamado a primitivas.

  • La última columna indica información de la función y la línea en la que se encuentra dentro del código. Por ejemplo la línea $1$ de módulo es el llamado a la función __main__. La línea $2$ es el llamado a la función Rcf. Por lo que es prácticamente negligible el llamado a __main__.


In [16]:
%%bash
python3 -m cProfile -s cumulative Rcf.py


aproximación: 7.468241e-01
         2000068 function calls in 0.649 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.649    0.649 {built-in method builtins.exec}
        1    0.021    0.021    0.649    0.649 Rcf.py:1(<module>)
        1    0.155    0.155    0.627    0.627 Rcf.py:2(Rcf)
  1000000    0.252    0.000    0.343    0.000 Rcf.py:23(<lambda>)
        1    0.129    0.129    0.129    0.129 Rcf.py:16(<listcomp>)
  1000000    0.090    0.000    0.090    0.000 {built-in method math.exp}
        1    0.000    0.000    0.001    0.001 <frozen importlib._bootstrap>:966(_find_and_load)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:936(_find_and_load_unlocked)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:651(_load_unlocked)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:564(module_from_spec)
        2    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:211(_call_with_frames_removed)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:728(create_module)
        1    0.000    0.000    0.000    0.000 {built-in method _imp.create_builtin}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:147(__enter__)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:157(_get_module_lock)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:58(__init__)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:870(_find_spec)
        1    0.000    0.000    0.000    0.000 {method 'format' of 'str' objects}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:151(__exit__)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:103(release)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:707(find_spec)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.print}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:318(__exit__)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:433(spec_from_loader)
        2    0.000    0.000    0.000    0.000 {built-in method _thread.allocate_lock}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:504(_init_module_attrs)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:847(__exit__)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:736(exec_module)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:78(acquire)
        2    0.000    0.000    0.000    0.000 {built-in method _thread.get_ident}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:232(_requires_builtin_wrapper)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.any}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:176(cb)
        4    0.000    0.000    0.000    0.000 {built-in method builtins.getattr}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:311(__enter__)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:369(__init__)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:222(_verbose_message)
        2    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}
        2    0.000    0.000    0.000    0.000 {method 'rpartition' of 'str' objects}
        1    0.000    0.000    0.000    0.000 {built-in method _imp.is_builtin}
        4    0.000    0.000    0.000    0.000 {built-in method builtins.hasattr}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:307(__init__)
        4    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:321(<genexpr>)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:416(parent)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:843(__enter__)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:143(__init__)
        3    0.000    0.000    0.000    0.000 {built-in method _imp.acquire_lock}
        3    0.000    0.000    0.000    0.000 {built-in method _imp.release_lock}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.000    0.000    0.000    0.000 {built-in method _imp.exec_builtin}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:424(has_location)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:753(is_package)


Nota: Recordar que el output de CProfile con la flag -s cumulative está ordenando por el gasto en tiempo de las funciones que son llamadas en el bloque de código analizado. No está ordenando por parent functions. Para tener un output en el que se tenga qué funciones llaman a qué otras se puede utilizar lo siguiente:


In [17]:
%%bash
python3 -m cProfile -o profile.stats Rcf.py


aproximación: 7.468241e-01

In [18]:
import pstats

In [19]:
p = pstats.Stats("profile.stats")
p.sort_stats("cumulative")


Out[19]:
<pstats.Stats at 0x7f9775b947f0>

In [20]:
p.print_stats()


Wed Jan 29 15:41:40 2020    profile.stats

         2000068 function calls in 0.587 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.587    0.587 {built-in method builtins.exec}
        1    0.014    0.014    0.587    0.587 Rcf.py:1(<module>)
        1    0.131    0.131    0.573    0.573 Rcf.py:2(Rcf)
  1000000    0.218    0.000    0.300    0.000 Rcf.py:23(<lambda>)
        1    0.142    0.142    0.142    0.142 Rcf.py:16(<listcomp>)
  1000000    0.082    0.000    0.082    0.000 {built-in method math.exp}
        1    0.000    0.000    0.001    0.001 <frozen importlib._bootstrap>:966(_find_and_load)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:936(_find_and_load_unlocked)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:651(_load_unlocked)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:564(module_from_spec)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:728(create_module)
        2    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:211(_call_with_frames_removed)
        1    0.000    0.000    0.000    0.000 {built-in method _imp.create_builtin}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:147(__enter__)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:870(_find_spec)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:157(_get_module_lock)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:707(find_spec)
        1    0.000    0.000    0.000    0.000 {method 'format' of 'str' objects}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:433(spec_from_loader)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:58(__init__)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:504(_init_module_attrs)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.print}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:318(__exit__)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:151(__exit__)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:78(acquire)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:103(release)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:232(_requires_builtin_wrapper)
        4    0.000    0.000    0.000    0.000 {built-in method builtins.hasattr}
        4    0.000    0.000    0.000    0.000 {built-in method builtins.getattr}
        1    0.000    0.000    0.000    0.000 {built-in method builtins.any}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:369(__init__)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:416(parent)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:736(exec_module)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:176(cb)
        1    0.000    0.000    0.000    0.000 {built-in method _imp.is_builtin}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:311(__enter__)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:143(__init__)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:222(_verbose_message)
        2    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:307(__init__)
        4    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:321(<genexpr>)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:847(__exit__)
        2    0.000    0.000    0.000    0.000 {method 'rpartition' of 'str' objects}
        2    0.000    0.000    0.000    0.000 {built-in method _thread.allocate_lock}
        2    0.000    0.000    0.000    0.000 {built-in method _thread.get_ident}
        3    0.000    0.000    0.000    0.000 {built-in method _imp.acquire_lock}
        3    0.000    0.000    0.000    0.000 {built-in method _imp.release_lock}
        1    0.000    0.000    0.000    0.000 {built-in method _imp.exec_builtin}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:843(__enter__)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:424(has_location)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:753(is_package)


Out[20]:
<pstats.Stats at 0x7f9775b947f0>

In [21]:
p.print_callers()


   Ordered by: cumulative time

Function                                                      was called by...
                                                                  ncalls  tottime  cumtime
{built-in method builtins.exec}                               <- 
Rcf.py:1(<module>)                                            <-       1    0.014    0.587  {built-in method builtins.exec}
Rcf.py:2(Rcf)                                                 <-       1    0.131    0.573  Rcf.py:1(<module>)
Rcf.py:23(<lambda>)                                           <- 1000000    0.218    0.300  Rcf.py:2(Rcf)
Rcf.py:16(<listcomp>)                                         <-       1    0.142    0.142  Rcf.py:2(Rcf)
{built-in method math.exp}                                    <- 1000000    0.082    0.082  Rcf.py:23(<lambda>)
<frozen importlib._bootstrap>:966(_find_and_load)             <-       1    0.000    0.001  Rcf.py:1(<module>)
<frozen importlib._bootstrap>:936(_find_and_load_unlocked)    <-       1    0.000    0.000  <frozen importlib._bootstrap>:966(_find_and_load)
<frozen importlib._bootstrap>:651(_load_unlocked)             <-       1    0.000    0.000  <frozen importlib._bootstrap>:936(_find_and_load_unlocked)
<frozen importlib._bootstrap>:564(module_from_spec)           <-       1    0.000    0.000  <frozen importlib._bootstrap>:651(_load_unlocked)
<frozen importlib._bootstrap>:728(create_module)              <-       1    0.000    0.000  <frozen importlib._bootstrap>:564(module_from_spec)
<frozen importlib._bootstrap>:211(_call_with_frames_removed)  <-       1    0.000    0.000  <frozen importlib._bootstrap>:728(create_module)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:736(exec_module)
{built-in method _imp.create_builtin}                         <-       1    0.000    0.000  <frozen importlib._bootstrap>:211(_call_with_frames_removed)
<frozen importlib._bootstrap>:147(__enter__)                  <-       1    0.000    0.000  <frozen importlib._bootstrap>:966(_find_and_load)
<frozen importlib._bootstrap>:870(_find_spec)                 <-       1    0.000    0.000  <frozen importlib._bootstrap>:936(_find_and_load_unlocked)
<frozen importlib._bootstrap>:157(_get_module_lock)           <-       1    0.000    0.000  <frozen importlib._bootstrap>:147(__enter__)
<frozen importlib._bootstrap>:707(find_spec)                  <-       1    0.000    0.000  <frozen importlib._bootstrap>:870(_find_spec)
{method 'format' of 'str' objects}                            <-       1    0.000    0.000  Rcf.py:1(<module>)
<frozen importlib._bootstrap>:433(spec_from_loader)           <-       1    0.000    0.000  <frozen importlib._bootstrap>:707(find_spec)
<frozen importlib._bootstrap>:58(__init__)                    <-       1    0.000    0.000  <frozen importlib._bootstrap>:157(_get_module_lock)
<frozen importlib._bootstrap>:504(_init_module_attrs)         <-       1    0.000    0.000  <frozen importlib._bootstrap>:564(module_from_spec)
{built-in method builtins.print}                              <-       1    0.000    0.000  Rcf.py:1(<module>)
<frozen importlib._bootstrap>:318(__exit__)                   <-       1    0.000    0.000  <frozen importlib._bootstrap>:651(_load_unlocked)
<frozen importlib._bootstrap>:151(__exit__)                   <-       1    0.000    0.000  <frozen importlib._bootstrap>:966(_find_and_load)
<frozen importlib._bootstrap>:78(acquire)                     <-       1    0.000    0.000  <frozen importlib._bootstrap>:147(__enter__)
<frozen importlib._bootstrap>:103(release)                    <-       1    0.000    0.000  <frozen importlib._bootstrap>:151(__exit__)
<frozen importlib._bootstrap>:232(_requires_builtin_wrapper)  <-       1    0.000    0.000  <frozen importlib._bootstrap>:433(spec_from_loader)
{built-in method builtins.hasattr}                            <-       2    0.000    0.000  <frozen importlib._bootstrap>:433(spec_from_loader)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:564(module_from_spec)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:651(_load_unlocked)
{built-in method builtins.getattr}                            <-       4    0.000    0.000  <frozen importlib._bootstrap>:504(_init_module_attrs)
{built-in method builtins.any}                                <-       1    0.000    0.000  <frozen importlib._bootstrap>:318(__exit__)
<frozen importlib._bootstrap>:369(__init__)                   <-       1    0.000    0.000  <frozen importlib._bootstrap>:433(spec_from_loader)
<frozen importlib._bootstrap>:416(parent)                     <-       1    0.000    0.000  <frozen importlib._bootstrap>:504(_init_module_attrs)
<frozen importlib._bootstrap>:736(exec_module)                <-       1    0.000    0.000  <frozen importlib._bootstrap>:651(_load_unlocked)
<frozen importlib._bootstrap>:176(cb)                         <-       1    0.000    0.000  <frozen importlib._bootstrap>:966(_find_and_load)
{built-in method _imp.is_builtin}                             <-       1    0.000    0.000  <frozen importlib._bootstrap>:707(find_spec)
<frozen importlib._bootstrap>:311(__enter__)                  <-       1    0.000    0.000  <frozen importlib._bootstrap>:651(_load_unlocked)
<frozen importlib._bootstrap>:143(__init__)                   <-       1    0.000    0.000  <frozen importlib._bootstrap>:966(_find_and_load)
<frozen importlib._bootstrap>:222(_verbose_message)           <-       1    0.000    0.000  <frozen importlib._bootstrap>:318(__exit__)
{method 'get' of 'dict' objects}                              <-       1    0.000    0.000  <frozen importlib._bootstrap>:176(cb)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:966(_find_and_load)
<frozen importlib._bootstrap>:307(__init__)                   <-       1    0.000    0.000  <frozen importlib._bootstrap>:651(_load_unlocked)
<frozen importlib._bootstrap>:321(<genexpr>)                  <-       4    0.000    0.000  {built-in method builtins.any}
<frozen importlib._bootstrap>:847(__exit__)                   <-       1    0.000    0.000  <frozen importlib._bootstrap>:870(_find_spec)
{method 'rpartition' of 'str' objects}                        <-       1    0.000    0.000  <frozen importlib._bootstrap>:416(parent)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:936(_find_and_load_unlocked)
{built-in method _thread.allocate_lock}                       <-       2    0.000    0.000  <frozen importlib._bootstrap>:58(__init__)
{built-in method _thread.get_ident}                           <-       1    0.000    0.000  <frozen importlib._bootstrap>:78(acquire)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:103(release)
{built-in method _imp.acquire_lock}                           <-       1    0.000    0.000  <frozen importlib._bootstrap>:157(_get_module_lock)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:176(cb)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:843(__enter__)
{built-in method _imp.release_lock}                           <-       1    0.000    0.000  <frozen importlib._bootstrap>:157(_get_module_lock)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:176(cb)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:847(__exit__)
{built-in method _imp.exec_builtin}                           <-       1    0.000    0.000  <frozen importlib._bootstrap>:211(_call_with_frames_removed)
<frozen importlib._bootstrap>:843(__enter__)                  <-       1    0.000    0.000  <frozen importlib._bootstrap>:870(_find_spec)
{method 'disable' of '_lsprof.Profiler' objects}              <- 
<frozen importlib._bootstrap>:424(has_location)               <-       1    0.000    0.000  <frozen importlib._bootstrap>:504(_init_module_attrs)
<frozen importlib._bootstrap>:753(is_package)                 <-       1    0.000    0.000  <frozen importlib._bootstrap>:232(_requires_builtin_wrapper)


Out[21]:
<pstats.Stats at 0x7f9775b947f0>

y podemos también tener la información de a qué funciones llamó cada función


In [22]:
p.print_callees()


   Ordered by: cumulative time

Function                                                      called...
                                                                  ncalls  tottime  cumtime
{built-in method builtins.exec}                               ->       1    0.014    0.587  Rcf.py:1(<module>)
Rcf.py:1(<module>)                                            ->       1    0.000    0.001  <frozen importlib._bootstrap>:966(_find_and_load)
                                                                       1    0.131    0.573  Rcf.py:2(Rcf)
                                                                       1    0.000    0.000  {built-in method builtins.print}
                                                                       1    0.000    0.000  {method 'format' of 'str' objects}
Rcf.py:2(Rcf)                                                 ->       1    0.142    0.142  Rcf.py:16(<listcomp>)
                                                                 1000000    0.218    0.300  Rcf.py:23(<lambda>)
Rcf.py:23(<lambda>)                                           -> 1000000    0.082    0.082  {built-in method math.exp}
Rcf.py:16(<listcomp>)                                         -> 
{built-in method math.exp}                                    -> 
<frozen importlib._bootstrap>:966(_find_and_load)             ->       1    0.000    0.000  <frozen importlib._bootstrap>:143(__init__)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:147(__enter__)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:151(__exit__)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:176(cb)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:936(_find_and_load_unlocked)
                                                                       1    0.000    0.000  {method 'get' of 'dict' objects}
<frozen importlib._bootstrap>:936(_find_and_load_unlocked)    ->       1    0.000    0.000  <frozen importlib._bootstrap>:651(_load_unlocked)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:870(_find_spec)
                                                                       1    0.000    0.000  {method 'rpartition' of 'str' objects}
<frozen importlib._bootstrap>:651(_load_unlocked)             ->       1    0.000    0.000  <frozen importlib._bootstrap>:307(__init__)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:311(__enter__)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:318(__exit__)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:564(module_from_spec)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:736(exec_module)
                                                                       1    0.000    0.000  {built-in method builtins.hasattr}
<frozen importlib._bootstrap>:564(module_from_spec)           ->       1    0.000    0.000  <frozen importlib._bootstrap>:504(_init_module_attrs)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:728(create_module)
                                                                       1    0.000    0.000  {built-in method builtins.hasattr}
<frozen importlib._bootstrap>:728(create_module)              ->       1    0.000    0.000  <frozen importlib._bootstrap>:211(_call_with_frames_removed)
<frozen importlib._bootstrap>:211(_call_with_frames_removed)  ->       1    0.000    0.000  {built-in method _imp.create_builtin}
                                                                       1    0.000    0.000  {built-in method _imp.exec_builtin}
{built-in method _imp.create_builtin}                         -> 
<frozen importlib._bootstrap>:147(__enter__)                  ->       1    0.000    0.000  <frozen importlib._bootstrap>:78(acquire)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:157(_get_module_lock)
<frozen importlib._bootstrap>:870(_find_spec)                 ->       1    0.000    0.000  <frozen importlib._bootstrap>:707(find_spec)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:843(__enter__)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:847(__exit__)
<frozen importlib._bootstrap>:157(_get_module_lock)           ->       1    0.000    0.000  <frozen importlib._bootstrap>:58(__init__)
                                                                       1    0.000    0.000  {built-in method _imp.acquire_lock}
                                                                       1    0.000    0.000  {built-in method _imp.release_lock}
<frozen importlib._bootstrap>:707(find_spec)                  ->       1    0.000    0.000  <frozen importlib._bootstrap>:433(spec_from_loader)
                                                                       1    0.000    0.000  {built-in method _imp.is_builtin}
{method 'format' of 'str' objects}                            -> 
<frozen importlib._bootstrap>:433(spec_from_loader)           ->       1    0.000    0.000  <frozen importlib._bootstrap>:232(_requires_builtin_wrapper)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:369(__init__)
                                                                       2    0.000    0.000  {built-in method builtins.hasattr}
<frozen importlib._bootstrap>:58(__init__)                    ->       2    0.000    0.000  {built-in method _thread.allocate_lock}
<frozen importlib._bootstrap>:504(_init_module_attrs)         ->       1    0.000    0.000  <frozen importlib._bootstrap>:416(parent)
                                                                       1    0.000    0.000  <frozen importlib._bootstrap>:424(has_location)
                                                                       4    0.000    0.000  {built-in method builtins.getattr}
{built-in method builtins.print}                              -> 
<frozen importlib._bootstrap>:318(__exit__)                   ->       1    0.000    0.000  <frozen importlib._bootstrap>:222(_verbose_message)
                                                                       1    0.000    0.000  {built-in method builtins.any}
<frozen importlib._bootstrap>:151(__exit__)                   ->       1    0.000    0.000  <frozen importlib._bootstrap>:103(release)
<frozen importlib._bootstrap>:78(acquire)                     ->       1    0.000    0.000  {built-in method _thread.get_ident}
<frozen importlib._bootstrap>:103(release)                    ->       1    0.000    0.000  {built-in method _thread.get_ident}
<frozen importlib._bootstrap>:232(_requires_builtin_wrapper)  ->       1    0.000    0.000  <frozen importlib._bootstrap>:753(is_package)
{built-in method builtins.hasattr}                            -> 
{built-in method builtins.getattr}                            -> 
{built-in method builtins.any}                                ->       4    0.000    0.000  <frozen importlib._bootstrap>:321(<genexpr>)
<frozen importlib._bootstrap>:369(__init__)                   -> 
<frozen importlib._bootstrap>:416(parent)                     ->       1    0.000    0.000  {method 'rpartition' of 'str' objects}
<frozen importlib._bootstrap>:736(exec_module)                ->       1    0.000    0.000  <frozen importlib._bootstrap>:211(_call_with_frames_removed)
<frozen importlib._bootstrap>:176(cb)                         ->       1    0.000    0.000  {built-in method _imp.acquire_lock}
                                                                       1    0.000    0.000  {built-in method _imp.release_lock}
                                                                       1    0.000    0.000  {method 'get' of 'dict' objects}
{built-in method _imp.is_builtin}                             -> 
<frozen importlib._bootstrap>:311(__enter__)                  -> 
<frozen importlib._bootstrap>:143(__init__)                   -> 
<frozen importlib._bootstrap>:222(_verbose_message)           -> 
{method 'get' of 'dict' objects}                              -> 
<frozen importlib._bootstrap>:307(__init__)                   -> 
<frozen importlib._bootstrap>:321(<genexpr>)                  -> 
<frozen importlib._bootstrap>:847(__exit__)                   ->       1    0.000    0.000  {built-in method _imp.release_lock}
{method 'rpartition' of 'str' objects}                        -> 
{built-in method _thread.allocate_lock}                       -> 
{built-in method _thread.get_ident}                           -> 
{built-in method _imp.acquire_lock}                           -> 
{built-in method _imp.release_lock}                           -> 
{built-in method _imp.exec_builtin}                           -> 
<frozen importlib._bootstrap>:843(__enter__)                  ->       1    0.000    0.000  {built-in method _imp.acquire_lock}
{method 'disable' of '_lsprof.Profiler' objects}              -> 
<frozen importlib._bootstrap>:424(has_location)               -> 
<frozen importlib._bootstrap>:753(is_package)                 -> 


Out[22]:
<pstats.Stats at 0x7f9775b947f0>

El comando de magic es %prun:


In [23]:
%prun -s cumulative Rcf(f,0,1,n)


 
         2000005 function calls in 0.765 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.765    0.765 {built-in method builtins.exec}
        1    0.020    0.020    0.765    0.765 <string>:1(<module>)
        1    0.192    0.192    0.745    0.745 <ipython-input-4-08ae95932785>:1(Rcf)
  1000000    0.319    0.000    0.426    0.000 <ipython-input-3-d9185a29ac70>:1(<lambda>)
        1    0.127    0.127    0.127    0.127 <ipython-input-4-08ae95932785>:15(<listcomp>)
  1000000    0.106    0.000    0.106    0.000 {built-in method math.exp}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

2) Uso de line_profiler

line_profiler trabaja perfilando el código de forma individual funciones línea por línea. La idea sería perfilar primero con CProfile al programa para identificar aquellas funciones que gastan un mayor tiempo de ejecución y posteriormente perfilarlas con line_profiler.

Comentario: una buena práctica es guardar las diferentes versiones de tu código cuando vas modificándolo para tener un registro de tus cambios.

Puede ejecutarse desde la línea de comandos o cargarse en IPython con el comando magic load_ext:


In [24]:
%load_ext line_profiler

In [25]:
%lprun?


Docstring:
Execute a statement under the line-by-line profiler from the
line_profiler module.

Usage:
  %lprun -f func1 -f func2 <statement>

The given statement (which doesn't require quote marks) is run via the
LineProfiler. Profiling is enabled for the functions specified by the -f
options. The statistics will be shown side-by-side with the code through the
pager once the statement has completed.

Options:

-f <function>: LineProfiler only profiles functions and methods it is told
to profile.  This option tells the profiler about these functions. Multiple
-f options may be used. The argument may be any expression that gives
a Python function or method object. However, one must be careful to avoid
spaces that may confuse the option parser.

-m <module>: Get all the functions/methods in a module

One or more -f or -m options are required to get any useful results.

-D <filename>: dump the raw statistics out to a pickle file on disk. The
usual extension for this is ".lprof". These statistics may be viewed later
by running line_profiler.py as a script.

-T <filename>: dump the text-formatted statistics with the code side-by-side
out to a text file.

-r: return the LineProfiler object after it has completed profiling.

-s: strip out all entries from the print-out that have zeros.

-u: specify time unit for the print-out in seconds.
File:      ~/.local/lib/python3.6/site-packages/line_profiler/line_profiler.py

En el siguiente output:

  • La columna %Time contiene el porcentaje de tiempo gastado. En el caso que se perfila, la líneasum_res=sum_res+f(node) es en la que más porcentaje del tiempo se gasta. Seguida de la línea del for y de la línea donde se hace uso de list comprehension para crear a los nodos de integración numérica.

In [26]:
%lprun -f Rcf Rcf(f,0,1,n)


Timer unit: 1e-06 s

Total time: 1.68936 s
File: <ipython-input-4-08ae95932785>
Function: Rcf at line 1

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     1                                           def Rcf(f,a,b,n): #Rcf: rectángulo compuesto para f
     2                                               """
     3                                               Compute numerical approximation using rectangle or mid-point method in 
     4                                               an interval.
     5                                               Nodes are generated via formula: x_i = a+(i+1/2)h_hat for i=0,1,...,n and h_hat=(b-a)/n
     6                                               Args:
     7                                                   f (lambda expression): lambda expression of integrand
     8                                                   a (int): left point of interval
     9                                                   b (int): right point of interval
    10                                                   n (int): number of subintervals
    11                                               Returns:
    12                                                   Rcf (float) 
    13                                               """
    14         1          2.0      2.0      0.0      h_hat=(b-a)/n
    15         1     275788.0 275788.0     16.3      nodes=[a+(i+1/2)*h_hat for i in range(0,n)]
    16         1          1.0      1.0      0.0      sum_res=0
    17   1000001     486769.0      0.5     28.8      for node in nodes:
    18   1000000     926802.0      0.9     54.9          sum_res=sum_res+f(node)
    19         1          1.0      1.0      0.0      return h_hat*sum_res

Con la evidencia generada con line_profiler ¿podríamos escribir una función que fuera más rápida?

Lo primero que podemos hacer es utilizar un generator en lugar de una lista:


In [27]:
def Rcf2(f,a,b,n):
    """
    Compute numerical approximation using rectangle or mid-point method in 
    an interval.
    Nodes are generated via formula: x_i = a+(b-a)/n*i for i=0,1,...,n-1
    Args:
        f (lambda expression): lambda expression of integrand
        a (int): left point of interval
        b (int): right point of interval
        n (int): number of subintervals
    Returns:
        Rcf2 (float) 
    """
    h_hat=(b-a)/n
    nodes=(a+(i+1/2)*h_hat for i in range(0,n))
    sum_res=0
    for node in nodes:
        sum_res=sum_res+f(node)
    return h_hat*sum_res

medir con %timeit:


In [28]:
%timeit -n 5 -r 10 Rcf2(f,0,1,n)


339 ms ± 16.9 ms per loop (mean ± std. dev. of 10 runs, 5 loops each)

In [42]:
aprox=Rcf2(f,0,1,n)

revisar que está correcta esta nueva implementación:


In [43]:
err_relativo(aprox,obj)


Out[43]:
6.71939731300312e-14

perfilarla con line_profiler:


In [44]:
%lprun -f Rcf2 Rcf2(f,0,1,n)


Timer unit: 1e-06 s

Total time: 1.99524 s
File: <ipython-input-27-794f7be94ca8>
Function: Rcf2 at line 1

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     1                                           def Rcf2(f,a,b,n):
     2                                               """
     3                                               Compute numerical approximation using rectangle or mid-point method in 
     4                                               an interval.
     5                                               Nodes are generated via formula: x_i = a+(b-a)/n*i for i=0,1,...,n
     6                                               Args:
     7                                                   f (lambda expression): lambda expression of integrand
     8                                                   a (int): left point of interval
     9                                                   b (int): right point of interval
    10                                                   n (int): number of subintervals
    11                                               Returns:
    12                                                   Rcf2 (float) 
    13                                               """
    14         1          3.0      3.0      0.0      h_hat=(b-a)/n
    15         1          4.0      4.0      0.0      nodes=(a+(i+1/2)*h_hat for i in range(0,n))
    16         1          0.0      0.0      0.0      sum_res=0
    17   1000001     945299.0      0.9     47.4      for node in nodes:
    18   1000000    1049935.0      1.0     52.6          sum_res=sum_res+f(node)
    19         1          1.0      1.0      0.0      return h_hat*sum_res

y observar que la línea en la que se creaba la lista ahora es despreciable el porcentaje de tiempo que se gasta en ella.

Podemos hacer una implementación que se encargue del gasto del tiempo en la línea del for:


In [31]:
def Rcf3(f,a,b,n):
    """
    Compute numerical approximation using rectangle or mid-point method in 
    an interval.
    Nodes are generated via formula: x_i = a+(b-a)/n*i for i=0,1,...,n-1
    Args:
        f (lambda expression): lambda expression of integrand
        a (int): left point of interval
        b (int): right point of interval
        n (int): number of subintervals
    Returns:
        Rcf3 (float) 
    """
    h_hat=(b-a)/n
    nodes=(a+(i+1/2)*h_hat for i in range(0,n))
    suma_res = sum((f(node) for node in nodes))
    return h_hat*suma_res

medir con %timeit:


In [32]:
%timeit -n 5 -r 10 Rcf3(f,0,1,n)


363 ms ± 4.99 ms per loop (mean ± std. dev. of 10 runs, 5 loops each)

revisar que está correcta esta nueva implementación:


In [45]:
aprox=Rcf3(f,0,1,n)

In [46]:
err_relativo(aprox,obj)


Out[46]:
6.71939731300312e-14

perfilarla con line_profiler:


In [47]:
%lprun -f Rcf3 Rcf3(f,0,1,n)


Timer unit: 1e-06 s

Total time: 1.27942 s
File: <ipython-input-31-0b5466de5036>
Function: Rcf3 at line 1

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     1                                           def Rcf3(f,a,b,n):
     2                                               """
     3                                               Compute numerical approximation using rectangle or mid-point method in 
     4                                               an interval.
     5                                               Nodes are generated via formula: x_i = a+(b-a)/n*i for i=0,1,...,n
     6                                               Args:
     7                                                   f (lambda expression): lambda expression of integrand
     8                                                   a (int): left point of interval
     9                                                   b (int): right point of interval
    10                                                   n (int): number of subintervals
    11                                               Returns:
    12                                                   Rcf3 (float) 
    13                                               """
    14         1          4.0      4.0      0.0      h_hat=(b-a)/n
    15         1          5.0      5.0      0.0      nodes=(a+(i+1/2)*h_hat for i in range(0,n))
    16         1    1279413.0 1279413.0    100.0      suma_res = sum((f(node) for node in nodes))
    17         1          2.0      2.0      0.0      return h_hat*suma_res

y se tiene la mayoría del porcentaje de tiempo ahora en una sola línea.

Recuérdese que el resultado de Cprofile indicó que se llama a la función lambda y math.exp $n=10^6$ veces. Una implementación de la regla del rectángulo con menor número de llamadas a funciones (y por tanto menor tiempo) sería:


In [35]:
def Rcf4(a,b,n):
    """
    Compute numerical approximation using rectangle or mid-point method in 
    an interval.
    Nodes are generated via formula: x_i = a+(b-a)/n*i for i=0,1,...,n-1
    Args:
        f (lambda expression): lambda expression of integrand
        a (int): left point of interval
        b (int): right point of interval
        n (int): number of subintervals
    Returns:
        Rcf4 (float) 
    """
    h_hat=(b-a)/n
    nodes=(a+(i+1/2)*h_hat for i in range(0,n))
    suma_res = sum(((math.exp(-node**2) for node in nodes)))
    return h_hat*suma_res

In [36]:
%lprun -f Rcf4 Rcf4(0,1,n)


Timer unit: 1e-06 s

Total time: 0.826071 s
File: <ipython-input-35-ef8e351f19bf>
Function: Rcf4 at line 1

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     1                                           def Rcf4(a,b,n):
     2                                               """
     3                                               Compute numerical approximation using rectangle or mid-point method in 
     4                                               an interval.
     5                                               Nodes are generated via formula: x_i = a+(b-a)/n*i for i=0,1,...,n
     6                                               Mid point is calculated via formula: x_{i-1}+(x_i-x_{i-1})/2 for i=1,...,n to avoid rounding errors
     7                                               Args:
     8                                                   f (lambda expression): lambda expression of integrand
     9                                                   a (int): left point of interval
    10                                                   b (int): right point of interval
    11                                                   n (int): number of subintervals
    12                                               Returns:
    13                                                   Rcf4 (float) 
    14                                               """
    15         1          3.0      3.0      0.0      h_hat=(b-a)/n
    16         1          5.0      5.0      0.0      nodes=(a+(i+1/2)*h_hat for i in range(0,n))
    17         1     826061.0 826061.0    100.0      suma_res = sum(((math.exp(-node**2) for node in nodes)))
    18         1          2.0      2.0      0.0      return h_hat*suma_res

In [37]:
%timeit -n 5 -r 10 Rcf4(0,1,n)


308 ms ± 5.53 ms per loop (mean ± std. dev. of 10 runs, 5 loops each)

In [49]:
aprox=Rcf4(0,1,n)

In [50]:
err_relativo(aprox,obj)


Out[50]:
6.71939731300312e-14

Si bien esta implementación es la más rápida hasta este punto no es tan flexible pues está calculando la regla del rectángulo para una función definida dentro de la misma función. Si quisiéramos calcular la regla para otra función se tendría que directamente modificar la función Rcf lo cual no es flexible. Aunque Rcf4 es más rápida preferimos Rcf3 por su flexibilidad y menor uso de recursos (que se verá con el memory_profiler más adelante).


In [57]:
def Rcf5(a,b,n):
    """
    Compute numerical approximation using rectangle or mid-point method in 
    an interval.
    Nodes are generated via formula: x_i = a+(b-a)/n*i for i=0,1,...,n-1
    Args:
        f (lambda expression): lambda expression of integrand
        a (int): left point of interval
        b (int): right point of interval
        n (int): number of subintervals
    Returns:
        Rcf4 (float) 
    """
    h_hat=(b-a)/n
    f_nodes=(math.exp(-(a+(i+1/2)*h_hat)**2) for i in range(0,n))
    suma_res = sum(f_nodes)
    return h_hat*suma_res

In [58]:
%lprun -f Rcf5 Rcf5(0,1,n)


Timer unit: 1e-06 s

Total time: 0.579516 s
File: <ipython-input-57-9209a967c3ab>
Function: Rcf5 at line 1

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     1                                           def Rcf5(a,b,n):
     2                                               """
     3                                               Compute numerical approximation using rectangle or mid-point method in 
     4                                               an interval.
     5                                               Nodes are generated via formula: x_i = a+(b-a)/n*i for i=0,1,...,n-1
     6                                               Args:
     7                                                   f (lambda expression): lambda expression of integrand
     8                                                   a (int): left point of interval
     9                                                   b (int): right point of interval
    10                                                   n (int): number of subintervals
    11                                               Returns:
    12                                                   Rcf4 (float) 
    13                                               """
    14         1          5.0      5.0      0.0      h_hat=(b-a)/n
    15         1          5.0      5.0      0.0      f_nodes=(math.exp(-(a+(i+1/2)*h_hat)**2) for i in range(0,n))
    16         1     579504.0 579504.0    100.0      suma_res = sum(f_nodes)
    17         1          2.0      2.0      0.0      return h_hat*suma_res

In [59]:
%timeit -n 5 -r 10 Rcf5(0,1,n)


262 ms ± 7.36 ms per loop (mean ± std. dev. of 10 runs, 5 loops each)

In [60]:
aprox=Rcf5(0,1,n)

In [61]:
err_relativo(aprox,obj)


Out[61]:
6.71939731300312e-14

Obsérvese que en una línea se están construyendo nodos y transformando con math.exp en Rcf5. Aunque esta implementación es la más rápida hasta ahora, no se sugiere usarla pues le falta flexibilidad como Rcf4 y no es recomendable en una línea construir datos y transformarlos. Combinar operaciones en una sola línea resulta en código difícil de leer. Es mejor separar en dos funciones estas dos tareas por si falla una sepamos cuál falló y por qué falló.

Ejemplo de ejecución de line_profiler desde la línea de comandos:


In [62]:
%%file Rcf4.py
import math
@profile #esta línea es necesaria para indicar que la siguiente función 
         #desea perfilarse con line_profiler
def Rcf4(a,b,n):
    """
    Compute numerical approximation using rectangle or mid-point method in 
    an interval.
    Nodes are generated via formula: x_i = a+(b-a)/n*i for i=0,1,...,n
    Mid point is calculated via formula: x_{i-1}+(x_i-x_{i-1})/2 for i=1,...,n-1 to avoid rounding errors
    Args:
        f (lambda expression): lambda expression of integrand
        a (int): left point of interval
        b (int): right point of interval
        n (int): number of subintervals
    Returns:
        Rcf4 (float) 
    """
    h_hat=(b-a)/n
    nodes=(a+(i+1/2)*h_hat for i in range(0,n))
    suma_res = sum(((math.exp(-node**2) for node in nodes)))
    return h_hat*suma_res
if __name__ == "__main__":
    n=10**6
    print("aproximación: {:0.6e}".format(Rcf4(0,1,n)))


Overwriting Rcf4.py

In [63]:
%%bash
$HOME/.local/bin/kernprof -l -v Rcf4.py


aproximación: 7.468241e-01
Wrote profile results to Rcf4.py.lprof
Timer unit: 1e-06 s

Total time: 0.77406 s
File: Rcf4.py
Function: Rcf4 at line 2

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     2                                           @profile #esta línea es necesaria para indicar que la siguiente función 
     3                                                    #desea perfilarse con line_profiler
     4                                           def Rcf4(a,b,n):
     5                                               """
     6                                               Compute numerical approximation using rectangle or mid-point method in 
     7                                               an interval.
     8                                               Nodes are generated via formula: x_i = a+(b-a)/n*i for i=0,1,...,n
     9                                               Mid point is calculated via formula: x_{i-1}+(x_i-x_{i-1})/2 for i=1,...,n-1 to avoid rounding errors
    10                                               Args:
    11                                                   f (lambda expression): lambda expression of integrand
    12                                                   a (int): left point of interval
    13                                                   b (int): right point of interval
    14                                                   n (int): number of subintervals
    15                                               Returns:
    16                                                   Rcf4 (float) 
    17                                               """
    18         1          2.0      2.0      0.0      h_hat=(b-a)/n
    19         1          5.0      5.0      0.0      nodes=(a+(i+1/2)*h_hat for i in range(0,n))
    20         1     774051.0 774051.0    100.0      suma_res = sum(((math.exp(-node**2) for node in nodes)))
    21         1          2.0      2.0      0.0      return h_hat*suma_res

Observese en el output de CProfile siguiente para la función Rcf4 que las líneas con mayor gasto en el tiempo total son:

    nodes=(a+(i+1/2)*h_hat for i in range(0,n))
    suma_res = sum(((math.exp(-node**2) for node in nodes)))

In [64]:
import math
def Rcf4(a,b,n):
    """
    Compute numerical approximation using rectangle or mid-point method in 
    an interval.
    Nodes are generated via formula: x_i = a+(b-a)/n*i for i=0,1,...,n
    Mid point is calculated via formula: x_{i-1}+(x_i-x_{i-1})/2 for i=1,...,n-1 to avoid rounding errors
    Args:
        f (lambda expression): lambda expression of integrand
        a (int): left point of interval
        b (int): right point of interval
        n (int): number of subintervals
    Returns:
        Rcf4 (float) 
    """
    h_hat=(b-a)/n
    nodes=(a+(i+1/2)*h_hat for i in range(0,n))
    suma_res = sum(((math.exp(-node**2) for node in nodes)))
    return h_hat*suma_res

In [65]:
%prun -s cumulative Rcf4(0,1,n)


 
         3000007 function calls in 0.748 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.748    0.748 {built-in method builtins.exec}
        1    0.000    0.000    0.748    0.748 <string>:1(<module>)
        1    0.000    0.000    0.748    0.748 <ipython-input-64-07d4a7504f4f>:2(Rcf4)
        1    0.085    0.085    0.748    0.748 {built-in method builtins.sum}
  1000001    0.370    0.000    0.663    0.000 <ipython-input-64-07d4a7504f4f>:18(<genexpr>)
  1000001    0.196    0.000    0.196    0.000 <ipython-input-64-07d4a7504f4f>:17(<genexpr>)
  1000000    0.097    0.000    0.097    0.000 {built-in method math.exp}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

Uso de memoria RAM

Al realizar análisis del uso de memoria de tu código podemos responder preguntas como:

  • ¿Es posible utilizar menos RAM al reescribir mi función para que trabaje más eficientemente?

  • ¿Podemos usar más RAM para aprovechar mejor el uso del caché?

1)Uso de %memit

Es equivalente a %timeit en el sentido que realiza una serie de repeticiones para obtener un resultado estable del bloque de código analizado.


In [66]:
%load_ext memory_profiler

In [67]:
%memit?


Docstring:
Measure memory usage of a Python statement

Usage, in line mode:
  %memit [-r<R>t<T>i<I>] statement

Usage, in cell mode:
  %%memit [-r<R>t<T>i<I>] setup_code
  code...
  code...

This function can be used both as a line and cell magic:

- In line mode you can measure a single-line statement (though multiple
  ones can be chained with using semicolons).

- In cell mode, the statement in the first line is used as setup code
  (executed but not measured) and the body of the cell is measured.
  The cell body has access to any variables created in the setup code.

Options:
-r<R>: repeat the loop iteration <R> times and take the best result.
Default: 1

-t<T>: timeout after <T> seconds. Default: None

-i<I>: Get time information at an interval of I times per second.
    Defaults to 0.1 so that there is ten measurements per second.

-c: If present, add the memory usage of any children process to the report.

-o: If present, return a object containing memit run details

-q: If present, be quiet and do not output a result.

Examples
--------
::

  In [1]: %memit range(10000)
  peak memory: 21.42 MiB, increment: 0.41 MiB

  In [2]: %memit range(1000000)
  peak memory: 52.10 MiB, increment: 31.08 MiB

  In [3]: %%memit l=range(1000000)
     ...: len(l)
     ...:
  peak memory: 52.14 MiB, increment: 0.08 MiB
File:      ~/.local/lib/python3.6/site-packages/memory_profiler.py

Primero medimos cuánto RAM está utilizando el proceso del notebook:


In [68]:
%memit #how much RAM this process is consuming


peak memory: 119.73 MiB, increment: 0.00 MiB

Y podemos realizar mediciones para cada una de las implementaciones de la regla del rectángulo:


In [74]:
%memit -c Rcf(f,0,1,n)


peak memory: 244.84 MiB, increment: 119.38 MiB

In [75]:
%memit -c Rcf2(f,0,1,n)


peak memory: 228.70 MiB, increment: 102.99 MiB

In [76]:
%memit -c Rcf3(f,0,1,10**5)


peak memory: 228.77 MiB, increment: 103.05 MiB

In [77]:
%memit -c Rcf4(0,1,10**5)


peak memory: 228.83 MiB, increment: 103.12 MiB

In [78]:
%memit -c Rcf5(0,1,10**5)


peak memory: 228.77 MiB, increment: 103.05 MiB

El uso de generators nos ayuda a disminuir la cantidad de memoria RAM usada por nuestro proceso.

2) Uso de memory_profiler

Para medición de memoria línea por línea utilizamos memory_profiler. Se ejecuta más lento que line_profiler (entre $10$ y $100$ veces más lento!) y mejora su velocidad de ejecución al instalar el paquete psutil.

Con línea de comandos se ejecuta como sigue:


In [49]:
%%file Rcf_memory_profiler.py
import math
@profile #esta línea es necesaria para indicar que la siguiente función 
         #desea perfilarse con memory_profiler
def Rcf(f,a,b,n): #Rcf: rectángulo compuesto para f
    """
    Compute numerical approximation using rectangle or mid-point method in 
    an interval.
    Nodes are generated via formula: x_i = a+(i+1/2)h_hat for i=0,1,...,n and h_hat=(b-a)/n
    Args:
        f (lambda expression): lambda expression of integrand
        a (int): left point of interval
        b (int): right point of interval
        n (int): number of subintervals
    Returns:
        Rcf (float) 
    """
    h_hat=(b-a)/n
    nodes=[a+(i+1/2)*h_hat for i in range(0,n)]
    sum_res=0
    for node in nodes:
        sum_res=sum_res+f(node)
    return h_hat*sum_res
if __name__=="__main__": #añadimos este bloque para ejecución de la función Rcf
        n=10**6
        f=lambda x: math.exp(-x**2)
        print("aproximación: {:0.6e}".format(Rcf(f,0,1,n)))


Writing Rcf_memory_profiler.py

En el output siguiente se observa que la línea que más incrementa la cantidad de RAM alojada para el proceso que contiene la ejecución de la función Rcf es la creación de la lista de nodos nodes=[a+(i+1/2)*h_hat for i in range(0,n)]. Cuidado: el valor de la columna Increment para esta línea no necesariamente indica que la lista nodes ocupa en memoria $512 MB$'s, sólo que para la alocación de la lista el proceso creció en $512 MB$'s

Nota: en el output aparece $MiB$ que son mebibytes. Aunque no se cumple que un mebibyte sea igual a un megabyte, se toma en este comentario como megabytes pues la diferencia entre estas unidades es sutil.


In [50]:
%%bash
python3 -m memory_profiler Rcf_memory_profiler.py


aproximación: 7.468241e-01
Filename: Rcf_memory_profiler.py

Line #    Mem usage    Increment   Line Contents
================================================
     2   37.750 MiB   37.750 MiB   @profile #esta línea es necesaria para indicar que la siguiente función 
     3                                      #desea perfilarse con memory_profiler
     4                             def Rcf(f,a,b,n): #Rcf: rectángulo compuesto para f
     5                                 """
     6                                 Compute numerical approximation using rectangle or mid-point method in 
     7                                 an interval.
     8                                 Nodes are generated via formula: x_i = a+(i+1/2)h_hat for i=0,1,...,n and h_hat=(b-a)/n
     9                                 Args:
    10                                     f (lambda expression): lambda expression of integrand
    11                                     a (int): left point of interval
    12                                     b (int): right point of interval
    13                                     n (int): number of subintervals
    14                                 Returns:
    15                                     Rcf (float) 
    16                                 """
    17   37.750 MiB    0.000 MiB       h_hat=(b-a)/n
    18   69.012 MiB    0.512 MiB       nodes=[a+(i+1/2)*h_hat for i in range(0,n)]
    19   69.012 MiB    0.000 MiB       sum_res=0
    20   69.012 MiB    0.000 MiB       for node in nodes:
    21   69.012 MiB    0.000 MiB           sum_res=sum_res+f(node)
    22   69.012 MiB    0.000 MiB       return h_hat*sum_res


Como ya se había notado, los generators ahorran memoria:


In [51]:
%%file Rcf3_memory_profiler.py
import math
@profile #esta línea es necesaria para indicar que la siguiente función 
         #desea perfilarse con memory_profiler
def Rcf3(f,a,b,n):
    """
    Compute numerical approximation using rectangle or mid-point method in 
    an interval.
    Nodes are generated via formula: x_i = a+(b-a)/n*i for i=0,1,...,n
    Args:
        f (lambda expression): lambda expression of integrand
        a (int): left point of interval
        b (int): right point of interval
        n (int): number of subintervals
    Returns:
        Rcf3 (float) 
    """
    h_hat=(b-a)/n
    nodes=(a+(i+1/2)*h_hat for i in range(0,n))
    suma_res = sum((f(node) for node in nodes))
    return h_hat*suma_res
if __name__=="__main__": #añadimos este bloque para ejecución de la función Rcf3
        n=10**6
        f=lambda x: math.exp(-x**2)
        print("aproximación: {:0.6e}".format(Rcf3(f,0,1,n)))


Writing Rcf3_memory_profiler.py

En el output siguiente el proceso que involucra la ejecución de la función Rcf3 no incrementa el uso de memoria RAM por el uso de generators:


In [52]:
%%bash
python3 -m memory_profiler Rcf3_memory_profiler.py


aproximación: 7.468241e-01
Filename: Rcf3_memory_profiler.py

Line #    Mem usage    Increment   Line Contents
================================================
     2   37.590 MiB   37.590 MiB   @profile #esta línea es necesaria para indicar que la siguiente función 
     3                                      #desea perfilarse con memory_profiler
     4                             def Rcf3(f,a,b,n):
     5                                 """
     6                                 Compute numerical approximation using rectangle or mid-point method in 
     7                                 an interval.
     8                                 Nodes are generated via formula: x_i = a+(b-a)/n*i for i=0,1,...,n
     9                                 Args:
    10                                     f (lambda expression): lambda expression of integrand
    11                                     a (int): left point of interval
    12                                     b (int): right point of interval
    13                                     n (int): number of subintervals
    14                                 Returns:
    15                                     Rcf3 (float) 
    16                                 """
    17   37.590 MiB    0.000 MiB       h_hat=(b-a)/n
    18   37.590 MiB    0.000 MiB       nodes=(a+(i+1/2)*h_hat for i in range(0,n))
    19   37.590 MiB    0.000 MiB       suma_res = sum((f(node) for node in nodes))
    20   37.590 MiB    0.000 MiB       return h_hat*suma_res


3) Uso de heapy

Con heapy podemos revisar el número y tamaño de cada objeto que está en el heap de Python (ver liga y liga2 para memory management). También ayuda a encontrar memory leaks que ocurren si apuntamos a un objeto al que ya no deberíamos estar apuntando... ver liga3 para saber qué son las memory leaks.


In [53]:
import math
from guppy import hpy
def Rcf(f,a,b,n): #Rcf: rectángulo compuesto para f
    """
    Compute numerical approximation using rectangle or mid-point method in 
    an interval.
    Nodes are generated via formula: x_i = a+(i+1/2)h_hat for i=0,1,...,n and h_hat=(b-a)/n
    Args:
        f (lambda expression): lambda expression of integrand
        a (int): left point of interval
        b (int): right point of interval
        n (int): number of subintervals
    Returns:
        Rcf (float) 
    """
    hp=hpy()
    h_hat=(b-a)/n
    h=hp.heap()
    print("beginning of Rcf")
    print(h)
    nodes=[a+(i+1/2)*h_hat for i in range(0,n)]
    h=hp.heap()
    print("After creating list")
    print(h)
    sum_res=0
    for node in nodes:
        sum_res=sum_res+f(node)
    h=hp.heap()
    print("After loop")
    print(h)
    return h_hat*sum_res

In [54]:
Rcf(f,0,1,n)


beginning of Rcf
Partition of a set of 451943 objects. Total size = 56178528 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0 126625  28 17314268  31  17314268  31 str
     1 123042  27  9506096  17  26820364  48 tuple
     2  54023  12  4265424   8  31085788  55 bytes
     3  27255   6  3942936   7  35028724  62 types.CodeType
     4  25720   6  3497920   6  38526644  69 function
     5   3155   1  3112744   6  41639388  74 type
     6   6822   2  2831432   5  44470820  79 dict (no owner)
     7   1244   0  1935072   3  46405892  83 dict of module
     8   3155   1  1578376   3  47984268  85 dict of type
     9   2286   1   846912   2  48831180  87 set
<1047 more rows. Type e.g. '_.more' to view.>
After creating list
Partition of a set of 1451953 objects. Total size = 88876740 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0 1013609  70 24326616  27  24326616  27 float
     1 126625   9 17314268  19  41640884  47 str
     2   6796   0  9541384  11  51182268  58 list
     3 123043   8  9506152  11  60688420  68 tuple
     4  54023   4  4265424   5  64953844  73 bytes
     5  27255   2  3942936   4  68896780  78 types.CodeType
     6  25720   2  3497920   4  72394700  81 function
     7   3155   0  3112744   4  75507444  85 type
     8   6823   0  2831672   3  78339116  88 dict (no owner)
     9   1244   0  1935072   2  80274188  90 dict of module
<1048 more rows. Type e.g. '_.more' to view.>
After loop
Partition of a set of 1451947 objects. Total size = 88876124 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0 1013609  70 24326616  27  24326616  27 float
     1 126625   9 17314268  19  41640884  47 str
     2   6796   0  9541384  11  51182268  58 list
     3 123042   8  9506096  11  60688364  68 tuple
     4  54023   4  4265424   5  64953788  73 bytes
     5  27255   2  3942936   4  68896724  78 types.CodeType
     6  25720   2  3497920   4  72394644  81 function
     7   3155   0  3112744   4  75507388  85 type
     8   6822   0  2831432   3  78338820  88 dict (no owner)
     9   1244   0  1935072   2  80273892  90 dict of module
<1047 more rows. Type e.g. '_.more' to view.>
Out[54]:
0.7468241328124773

In [55]:
import math
from guppy import hpy
def Rcf3(f,a,b,n):
    """
    Compute numerical approximation using rectangle or mid-point method in 
    an interval.
    Nodes are generated via formula: x_i = a+(b-a)/n*i for i=0,1,...,n
    Args:
        f (lambda expression): lambda expression of integrand
        a (int): left point of interval
        b (int): right point of interval
        n (int): number of subintervals
    Returns:
        Rcf3 (float) 
    """
    hp=hpy()
    h_hat=(b-a)/n
    h=hp.heap()
    print("beginning of Rcf3")
    print(h)
    nodes=(a+(i+1/2)*h_hat for i in range(0,n))
    h=hp.heap()
    print("After creating generator")
    print(h)
    suma_res = sum((f(node) for node in nodes))
    h=hp.heap()
    print("After loop")
    print(h)
    return h_hat*suma_res

In [56]:
Rcf3(f,0,1,n)


beginning of Rcf3
Partition of a set of 451930 objects. Total size = 56178992 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0 126664  28 17317847  31  17317847  31 str
     1 123040  27  9506016  17  26823863  48 tuple
     2  54025  12  4265717   8  31089580  55 bytes
     3  27255   6  3942936   7  35032516  62 types.CodeType
     4  25716   6  3497376   6  38529892  69 function
     5   3155   1  3112744   6  41642636  74 type
     6   6819   2  2830712   5  44473348  79 dict (no owner)
     7   1244   0  1935072   3  46408420  83 dict of module
     8   3155   1  1578376   3  47986796  85 dict of type
     9   2286   1   846912   2  48833708  87 set
<1047 more rows. Type e.g. '_.more' to view.>
After creating generator
Partition of a set of 451952 objects. Total size = 56180784 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0 126664  28 17317847  31  17317847  31 str
     1 123041  27  9506072  17  26823919  48 tuple
     2  54025  12  4265717   8  31089636  55 bytes
     3  27255   6  3942936   7  35032572  62 types.CodeType
     4  25716   6  3497376   6  38529948  69 function
     5   3155   1  3112744   6  41642692  74 type
     6   6820   2  2830952   5  44473644  79 dict (no owner)
     7   1244   0  1935072   3  46408716  83 dict of module
     8   3155   1  1578376   3  47987092  85 dict of type
     9   2286   1   846912   2  48834004  87 set
<1049 more rows. Type e.g. '_.more' to view.>
After loop
Partition of a set of 451944 objects. Total size = 56179648 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0 126664  28 17317847  31  17317847  31 str
     1 123040  27  9506016  17  26823863  48 tuple
     2  54025  12  4265717   8  31089580  55 bytes
     3  27255   6  3942936   7  35032516  62 types.CodeType
     4  25716   6  3497376   6  38529892  69 function
     5   3155   1  3112744   6  41642636  74 type
     6   6819   2  2830712   5  44473348  79 dict (no owner)
     7   1244   0  1935072   3  46408420  83 dict of module
     8   3155   1  1578376   3  47986796  85 dict of type
     9   2286   1   846912   2  48833708  87 set
<1047 more rows. Type e.g. '_.more' to view.>
Out[56]:
0.7468241328124773

Como ya se había revisado el uso de generators ayuda a disminuir el consumo de memoria, manteniendo la eficiencia.

Ejercicios

  1. Realiza el análisis con las herramientas revisadas en esta nota para las reglas del trapecio y de Simpson de la nota 1.5.Integracion_numerica.

Referencias

  1. M. Gorelick, I. Ozsvald, High Performance Python, O'Reilly Media, 2014.

Otras referencias para heapy:

Ver SnakeViz para visualización del output de CProfile.