Compare commits
3 Commits
f8818bff93
...
5decb1d3ab
Author | SHA1 | Date | |
---|---|---|---|
5decb1d3ab | |||
a95bc5fe97 | |||
1a2c0816c3 |
3
python-perfs/.gitignore
vendored
3
python-perfs/.gitignore
vendored
|
@ -1,2 +1,5 @@
|
|||
.cache/
|
||||
.hypothesis/
|
||||
*.so
|
||||
*.c
|
||||
*.html
|
||||
|
|
|
@ -1,3 +1,17 @@
|
|||
#!/bin/sh
|
||||
printf "%s\n" "$*" >&2
|
||||
bkt --ttl 1y --cache-dir .cache "$@" 2>&1
|
||||
|
||||
ARGS="$*"
|
||||
|
||||
args="--ttl 1y --cache-dir .cache"
|
||||
|
||||
while [ "$1" != "--" ]
|
||||
do
|
||||
args="$args $1"
|
||||
shift
|
||||
done
|
||||
|
||||
before="$(date +"%s.%N")"
|
||||
bkt $args "$@" 2>&1
|
||||
after="$(date +"%s.%N")"
|
||||
|
||||
printf "%s: %.2fs\n\n" "$ARGS" "$(echo "$after - $before"|bc)" >&2
|
||||
|
|
7
python-perfs/include/collatz_length.py
Normal file
7
python-perfs/include/collatz_length.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
def collatz_length(n):
|
||||
if n == 1:
|
||||
return 0
|
||||
if n % 2 == 0:
|
||||
return 1 + collatz_length(n // 2)
|
||||
else:
|
||||
return 1 + collatz_length(n * 3 + 1)
|
10
python-perfs/include/collatz_length_annotated.py
Normal file
10
python-perfs/include/collatz_length_annotated.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
import cython
|
||||
|
||||
@cython.ccall
|
||||
def collatz_length(n: cython.uint) -> cython.uint:
|
||||
if n == 1:
|
||||
return 0
|
||||
if n % 2 == 0:
|
||||
return 1 + collatz_length(n // 2)
|
||||
else:
|
||||
return 1 + collatz_length(n * 3 + 1)
|
7
python-perfs/include/collatz_length_mypy.py
Normal file
7
python-perfs/include/collatz_length_mypy.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
def collatz_length(n: int) -> int:
|
||||
if n == 1:
|
||||
return 0
|
||||
if n % 2 == 0:
|
||||
return 1 + collatz_length(n // 2)
|
||||
else:
|
||||
return 1 + collatz_length(n * 3 + 1)
|
7
python-perfs/include/collatz_length_nuitka.py
Normal file
7
python-perfs/include/collatz_length_nuitka.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
def collatz_length(n):
|
||||
if n == 1:
|
||||
return 0
|
||||
if n % 2 == 0:
|
||||
return 1 + collatz_length(n // 2)
|
||||
else:
|
||||
return 1 + collatz_length(n * 3 + 1)
|
11
python-perfs/include/collatz_length_numba.py
Normal file
11
python-perfs/include/collatz_length_numba.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from numba import njit
|
||||
|
||||
|
||||
@njit
|
||||
def collatz_length(n):
|
||||
if n == 1:
|
||||
return 0
|
||||
if n % 2 == 0:
|
||||
return 1 + collatz_length(n // 2)
|
||||
else:
|
||||
return 1 + collatz_length(n * 3 + 1)
|
8
python-perfs/include/collatz_length_pythran.py
Normal file
8
python-perfs/include/collatz_length_pythran.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
#pythran export collatz_length(int)
|
||||
def collatz_length(n):
|
||||
if n == 1:
|
||||
return 0
|
||||
if n % 2 == 0:
|
||||
return 1 + collatz_length(n // 2)
|
||||
else:
|
||||
return 1 + collatz_length(n * 3 + 1)
|
|
@ -29,10 +29,11 @@ O(cⁿ) Exponentielle
|
|||
O(n!) Factorielle
|
||||
```
|
||||
|
||||
::: notes
|
||||
notes:
|
||||
|
||||
Il faut les grapher pour s'en rendre compte : cf. include/big.o.py
|
||||
|
||||
|
||||
## Comparaison asymptotique
|
||||
|
||||
Exemples.
|
||||
|
@ -52,7 +53,7 @@ def is_in(a_set: set, a_value):
|
|||
return a_value in a_set
|
||||
```
|
||||
|
||||
::: notes
|
||||
notes:
|
||||
|
||||
Peu importe la taille de la liste, accéder à un élément prend le même temps.
|
||||
|
||||
|
@ -63,7 +64,7 @@ Attention c'est toujours en base deux.
|
|||
|
||||
Exemple typique : chercher dans un annuaire.
|
||||
|
||||
::: notes
|
||||
notes:
|
||||
|
||||
Un annuaire deux fois plus gros ne vous demandera pas deux fois plus
|
||||
de temps mais peut-être une opération de plus.
|
||||
|
@ -90,12 +91,12 @@ Typique d'algorithmes de tris.
|
|||
|
||||
## Les mesures de complexité
|
||||
|
||||
- De temps (CPU consommé)
|
||||
- D'espace (Mémoire consommée)
|
||||
- Dans le meilleur des cas
|
||||
- Dans le pire des cas
|
||||
- Dans le cas moyen
|
||||
- Amorti
|
||||
- De temps (CPU consommé).
|
||||
- D'espace (Mémoire consommée).
|
||||
- Dans le meilleur des cas.
|
||||
- Dans le pire des cas.
|
||||
- Dans le cas moyen.
|
||||
- Amorti.
|
||||
- ...
|
||||
|
||||
|
||||
|
@ -119,22 +120,31 @@ Mais retenir par cœur la complexité de quelques structures
|
|||
- 1 nanoseconde (1 ns) c'est un milliardième de seconde.
|
||||
|
||||
|
||||
## Rappel des unités de temps
|
||||
|
||||
- milli c'est `10 ** -3`, c'est `0.001`.
|
||||
- micro c'est `10 ** -6`, c'est `0.000_001`.
|
||||
- nano c'est `10 ** -9`, c'est `0.000_000_001`.
|
||||
|
||||
|
||||
## Le cas typique
|
||||
|
||||
```bash
|
||||
$ python -m timeit -s 'container = list(range(10_000_000))' \
|
||||
'10_000_001 in container'
|
||||
#!cache -- python -m timeit -s 'container = list(range(10_000_000))' '10_000_001 in container'
|
||||
```shell
|
||||
$ python -m pyperf timeit \
|
||||
> --setup 'container = list(range(10_000_000))' \
|
||||
> '10_000_001 in container'
|
||||
#!cache -- python -m pyperf timeit --fast -s 'container = list(range(10_000_000))' '10_000_001 in container'
|
||||
|
||||
$ python -m timeit -s 'container = set(range(10_000_000))' \
|
||||
'10_000_001 in container'
|
||||
#!cache -- python -m timeit -n 100 -s 'container = set(range(10_000_000))' '10_000_001 in container'
|
||||
$ python -m pyperf timeit \
|
||||
> --setup 'container = set(range(10_000_000))' \
|
||||
> '10_000_001 in container'
|
||||
#!cache -- python -m pyperf timeit --fast -s 'container = set(range(10_000_000))' '10_000_001 in container'
|
||||
```
|
||||
|
||||
Pourquoi une si grande différence !?
|
||||
|
||||
|
||||
::: notes
|
||||
notes:
|
||||
|
||||
C'est l'heure du live coding !
|
||||
|
||||
|
@ -145,14 +155,14 @@ C'est l'heure du live coding !
|
|||
|
||||
`time`, un outil POSIX, mais aussi une fonction native de bash :
|
||||
|
||||
```bash
|
||||
```shell
|
||||
$ time python -c 'container = set(range(10_000_000))'
|
||||
#!cache -- time -p python -c 'container = set(range(10_000_000))' 2>&1
|
||||
#!cache -- time -p python -c 'container = set(range(10_000_000))'
|
||||
```
|
||||
|
||||
Mais `time` ne teste qu'une fois.
|
||||
|
||||
::: notes
|
||||
notes:
|
||||
|
||||
real 0m0.719s # C'est le temps « sur le mur »
|
||||
user 0m0.521s # Temps CPU passé « dans Python »
|
||||
|
@ -180,7 +190,7 @@ Benchmark 1: python -c pass
|
|||
Time (mean ± σ): 19.4 ms ± 0.6 ms
|
||||
```
|
||||
|
||||
::: notes
|
||||
notes:
|
||||
|
||||
N'essayez pas de retenir les chiffres, retenez les faits.
|
||||
|
||||
|
@ -199,7 +209,7 @@ Benchmark 1: /usr/bin/python3.10 -c pass
|
|||
Time (mean ± σ): 19.1 ms ± 0.8 ms
|
||||
```
|
||||
|
||||
::: notes
|
||||
notes:
|
||||
|
||||
Leur parler de `--enable-optimizations` (PGO).
|
||||
|
||||
|
@ -213,7 +223,7 @@ Timeit c'est dans la stdlib de Python, ça s'utilise en ligne de commande ou dep
|
|||
|
||||
C'est l'équivalent d'hyperfine mais exécutant du Python plutôt qu'un programme :
|
||||
|
||||
```bash
|
||||
```shell
|
||||
$ ~/.local/bin/python3.10 -m pyperf timeit pass
|
||||
.....................
|
||||
Mean +- std dev: 7.33 ns +- 0.18 ns
|
||||
|
@ -223,7 +233,7 @@ $ /usr/bin/python3.10 -m pyperf timeit pass
|
|||
Mean +- std dev: 6.10 ns +- 0.11 ns
|
||||
```
|
||||
|
||||
::: notes
|
||||
notes:
|
||||
|
||||
Avec hyperfine on teste combien de temps ça prend à Python **de
|
||||
démarrer** puis d'exécuter `pass`, ici on teste combien de temps ça
|
||||
|
@ -234,7 +244,7 @@ prend d'exécuter `pass`.
|
|||
|
||||
time, timeit, hyperfine, pyperf c'est bien pour mesurer, comparer.
|
||||
|
||||
cProfile nous aider à trouver la fonction coupable dans un script plus gros.
|
||||
cProfile nous aider à trouver la fonction coupable.
|
||||
|
||||
|
||||
## cProfile, exemple
|
||||
|
@ -331,7 +341,7 @@ En cachant `approx_phi` ?
|
|||
#!sed -n '10,/return step1/p' include/phi4.py
|
||||
```
|
||||
|
||||
::: notes
|
||||
notes:
|
||||
|
||||
Notez l'astuce pour que le `step2` d'un
|
||||
tour soit le `step1` du suivant...
|
||||
|
@ -346,6 +356,7 @@ $ python -m cProfile --sort cumulative phi4.py 2000
|
|||
de `fib` n'est pas chaud, et il peut vite devoir descendre
|
||||
profondément en récursion...
|
||||
|
||||
|
||||
## cProfile, exemple
|
||||
|
||||
Il est temps de sortir une implémentation de `fib` plus robuste, basée
|
||||
|
@ -363,35 +374,37 @@ $ python -m cProfile --sort cumulative phi5.py 2000
|
|||
#!cache -- python -m cProfile --sort cumulative include/phi5.py 2000 | head -n 2 | sed 's/^ *//g;s/seconds/s/g'
|
||||
```
|
||||
|
||||
::: notes
|
||||
notes:
|
||||
|
||||
Mieux.
|
||||
|
||||
|
||||
## Snakeviz
|
||||
|
||||
```text
|
||||
python -m pip install snakeviz
|
||||
#!python -m pip install snakeviz >/dev/null 2>&1
|
||||
python -m cProfile -o phi5.prof phi5.py 2000
|
||||
#!if [ ! -f /tmp/phi5.prof ]; then python -m cProfile -o /tmp/phi5.prof include/phi5.py 2000 >/dev/null 2>&1; fi
|
||||
#!if [ ! -f .cache/phi5.prof ]; then python -m cProfile -o .cache/phi5.prof include/phi5.py 2000 >/dev/null 2>&1; fi
|
||||
python -m snakeviz phi5.prof
|
||||
#!if [ ! -f .cache/phi5-snakeviz.png ]; then python -m snakeviz -s /tmp/phi5.prof & TOKILL=$!; sleep 1; cutycapt --min-width=1024 --delay=500 --url=http://127.0.0.1:8080/snakeviz/%2Ftmp%2Fphi5.prof --out=.cache/phi5-snakeviz.png ; kill $TOKILL; fi
|
||||
#!if [ ! -f output/phi5-snakeviz.png ]; then python -m snakeviz -s .cache/phi5.prof & TOKILL=$!; sleep 1; cutycapt --min-width=1024 --delay=500 --url=http://127.0.0.1:8080/snakeviz/%2Ftmp%2Fphi5.prof --out=output/phi5-snakeviz.png ; kill $TOKILL; fi
|
||||
```
|
||||
|
||||
|
||||
## Snakeviz
|
||||
|
||||
![](phi5-snakeviz.png)
|
||||
|
||||
|
||||
## Scalene
|
||||
|
||||
```bash
|
||||
```shell
|
||||
$ python -m pip install scalene
|
||||
#!python -m pip install scalene >/dev/null 2>&1
|
||||
$ scalene phi5.py 100000
|
||||
#!if [ ! -f .cache/phi5.html ]; then scalene include/phi5.py 100000 --html --outfile .cache/phi5.html --cli >&2; fi
|
||||
#!if [ ! -f .cache/phi5-scalene.png ]; then cutycapt --min-width=1024 --delay=100 --url=file://$(pwd)/.cache/phi5.html --out=.cache/phi5-scalene.png; fi
|
||||
##!if [ ! -f output/phi5.html ]; then scalene include/phi5.py 100000 --html --outfile output/phi5.html --cli >&2; fi
|
||||
##!if [ ! -f output/phi5-scalene.png ]; then cutycapt --min-width=1024 --delay=100 --url=file://$(pwd)/output/phi5.html --out=output/phi5-scalene.png; fi
|
||||
```
|
||||
|
||||
|
||||
## Scalene
|
||||
|
||||
![](phi5-scalene.png)
|
||||
|
@ -401,26 +414,173 @@ $ scalene phi5.py 100000
|
|||
|
||||
Générateur de prénoms français.
|
||||
|
||||
::: notes
|
||||
|
||||
See includes/prenom-*.py
|
||||
Notes: voir includes/prenom-*.py
|
||||
|
||||
|
||||
## TODO
|
||||
- vprof
|
||||
- https://pypi.org/project/pyflame/
|
||||
- https://pypi.org/project/pp3yflame/
|
||||
|
||||
|
||||
# Cython
|
||||
|
||||
Cython est un dialecte de Python transpilable en C.
|
||||
|
||||
## Cython démo
|
||||
|
||||
```python
|
||||
#!cat include/collatz_length.py
|
||||
```
|
||||
|
||||
## Cython démo
|
||||
|
||||
#!rm -f include/*.so # Ensure we're not hitting the cythonized one here...
|
||||
```shell
|
||||
$ python -m pyperf timeit \
|
||||
> -s 'from collatz_length import collatz_length'
|
||||
> 'collatz_length(837799)'
|
||||
#!cache -- python -m pyperf timeit --fast --setup 'from include.collatz_length import collatz_length' 'collatz_length(837799)'
|
||||
```
|
||||
|
||||
```shell
|
||||
$ cythonize --inplace collatz_length.py
|
||||
#!cythonize --inplace include/collatz_length.py
|
||||
```
|
||||
|
||||
|
||||
```shell
|
||||
$ python -m pyperf timeit \
|
||||
> -s 'from collatz_length import collatz_length'
|
||||
> 'collatz_length(837799)'
|
||||
#!cache -- python -m pyperf timeit --fast -s 'from include.collatz_length import collatz_length' 'collatz_length(837799)' # faster
|
||||
#!# Beware, the cythonized use `-s` while the non cythonized uses `--setup` just to have two cache buckets :D
|
||||
```
|
||||
|
||||
|
||||
## Cython annotate
|
||||
|
||||
```shell
|
||||
$ cython -a collatz_length.py
|
||||
#!cython -a include/collatz_length.py
|
||||
#!cutycapt --min-width=1024 --delay=500 --url=file://$(pwd)/include/collatz_length.html --out=output/collatz_length.png
|
||||
```
|
||||
|
||||
![](collatz_length.png)
|
||||
|
||||
|
||||
## Cython annotated
|
||||
|
||||
```python
|
||||
#!cat include/collatz_length_annotated.py
|
||||
```
|
||||
|
||||
```shell
|
||||
$ cythonize --inplace collatz_length_annotated.py
|
||||
```
|
||||
|
||||
#!cythonize --inplace include/collatz_length_annotated.py
|
||||
|
||||
```shell
|
||||
$ python -m pyperf timeit \
|
||||
> -s 'from collatz_length_annotated import collatz_length'
|
||||
> 'collatz_length(837799)'
|
||||
#!cache -- python -m pyperf timeit --fast -s 'from include.collatz_length_annotated import collatz_length' 'collatz_length(837799)'
|
||||
```
|
||||
|
||||
|
||||
## Cython annotate again
|
||||
|
||||
```shell
|
||||
$ cython -a include/collatz_length_annotated.py
|
||||
#!cython -a include/collatz_length_annotated.py
|
||||
#!cutycapt --min-width=1024 --delay=500 --url=file://$(pwd)/include/collatz_length_annotated.html --out=output/collatz_length_annotated.png
|
||||
```
|
||||
|
||||
![](collatz_length_annotated.png)
|
||||
|
||||
|
||||
# Numba
|
||||
|
||||
Numba est un `JIT` : « Just In Time compiler ».
|
||||
|
||||
```python
|
||||
#!cat include/collatz_length_numba.py
|
||||
```
|
||||
|
||||
## Numba démo
|
||||
|
||||
```shell
|
||||
$ python -m pyperf timeit \
|
||||
> -s 'from include.collatz_length_numba import collatz_length' \
|
||||
> 'collatz_length(837799)'
|
||||
#!cache -- python -m pyperf timeit --fast -s 'from include.collatz_length_numba import collatz_length' 'collatz_length(837799)'
|
||||
```
|
||||
|
||||
# mypyc
|
||||
|
||||
mypyc est un compilateur qui s'appuie sur les annotationes de type mypy :
|
||||
|
||||
```python
|
||||
#!cat include/collatz_length_mypy.py
|
||||
```
|
||||
|
||||
## mypyc demo
|
||||
|
||||
```shell
|
||||
$ mypyc include/collatz_length_mypy.py
|
||||
#!mypyc include/collatz_length_mypy.py
|
||||
```
|
||||
|
||||
```shell
|
||||
$ python -m pyperf timeit \
|
||||
> -s 'from collatz_length_mypy import collatz_length' \
|
||||
> 'collatz_length(837799)'
|
||||
#!cache -- python -m pyperf timeit --fast -s 'from collatz_length_mypy import collatz_length' 'collatz_length(837799)'
|
||||
```
|
||||
|
||||
|
||||
# Pythran
|
||||
|
||||
pythran est un compilateur pour du code scientifique :
|
||||
|
||||
```python
|
||||
#!cat include/collatz_length_pythran.py
|
||||
```
|
||||
|
||||
## Pythran demo
|
||||
|
||||
```shell
|
||||
$ pythran include/collatz_length_pythran.py
|
||||
#!pythran include/collatz_length_pythran.py
|
||||
```
|
||||
|
||||
```shell
|
||||
$ python -m pyperf timeit \
|
||||
> -s 'from collatz_length_pythran import collatz_length' \
|
||||
> 'collatz_length(837799)'
|
||||
#!cache -- python -m pyperf timeit --fast -s 'from collatz_length_pythran import collatz_length' 'collatz_length(837799)'
|
||||
```
|
||||
|
||||
|
||||
# Nuitka
|
||||
|
||||
# https://github.com/pfalcon/awesome-python-compilers
|
||||
Aussi un compilateur, aussi utilisable pour distribuer une application.
|
||||
|
||||
```shell
|
||||
$ python -m nuitka --module collatz_length_nuitka.py
|
||||
#!python -m nuitka --module include/collatz_length_nuitka.py >/dev/null
|
||||
```
|
||||
|
||||
```shell
|
||||
$ python -m pyperf timeit \
|
||||
> -s 'from collatz_length_nuitka import collatz_length' \
|
||||
> 'collatz_length(837799)'
|
||||
#!cache --force -- python -m pyperf timeit --fast -s 'from collatz_length_nuitka import collatz_length' 'collatz_length(837799)'
|
||||
```
|
||||
|
||||
|
||||
# Et d'autres
|
||||
|
||||
https://github.com/pfalcon/awesome-python-compilers
|
||||
|
||||
# Hand crafted C
|
||||
|
|
8
python-perfs/requirements.txt
Normal file
8
python-perfs/requirements.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
pyperf
|
||||
cython
|
||||
snakeviz
|
||||
scalene
|
||||
numba
|
||||
mypy
|
||||
pythran
|
||||
nuitka
|
Loading…
Reference in New Issue
Block a user