J'ai effectué le même benchmark que vous, en utilisant uniquement Python 3 :
$ docker run python:3-alpine3.6 python --version
Python 3.6.2
$ docker run python:3-slim python --version
Python 3.6.2
ce qui entraîne une différence de plus de 2 secondes :
$ docker run python:3-slim python -c "$BENCHMARK"
3.6475560404360294
$ docker run python:3-alpine3.6 python -c "$BENCHMARK"
5.834922112524509
Alpine utilise une implémentation différente de libc
(bibliothèque du système de base) de la musl projet ( URL miroir ). Il existe de nombreux les différences entre ces bibliothèques . Par conséquent, chaque bibliothèque peut être plus performante dans certains cas d'utilisation.
Voici un strace diff entre les commandes ci-dessus . La sortie commence à différer à partir de la ligne 269. Bien sûr, il y a des adresses différentes en mémoire, mais sinon c'est très similaire. La plus grande partie du temps est évidemment passée à attendre que la fonction python
pour terminer.
Après avoir installé strace
dans les deux conteneurs, nous pouvons obtenir une trace plus intéressante (J'ai réduit le nombre d'itérations dans le benchmark à 10).
Par exemple, glibc
charge les bibliothèques de la manière suivante (ligne 182) :
openat(AT_FDCWD, "/usr/local/lib/python3.6", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
getdents(3, /* 205 entries */, 32768) = 6824
getdents(3, /* 0 entries */, 32768) = 0
Le même code dans musl
:
open("/usr/local/lib/python3.6", O_RDONLY|O_DIRECTORY|O_CLOEXEC) = 3
fcntl(3, F_SETFD, FD_CLOEXEC) = 0
getdents64(3, /* 62 entries */, 2048) = 2040
getdents64(3, /* 61 entries */, 2048) = 2024
getdents64(3, /* 60 entries */, 2048) = 2032
getdents64(3, /* 22 entries */, 2048) = 728
getdents64(3, /* 0 entries */, 2048) = 0
Je ne dis pas que c'est la principale différence, mais la réduction du nombre d'opérations d'E/S dans les bibliothèques centrales pourrait contribuer à une meilleure performance. D'après la différence, vous pouvez voir que l'exécution du même code Python peut conduire à des appels système légèrement différents. Le plus important pourrait probablement être fait en optimisant les performances des boucles. Je ne suis pas assez qualifié pour juger si le problème de performance est dû à l'allocation de mémoire ou à une autre instruction.
-
glibc
avec 10 itérations :
write(1, "0.032388824969530106\n", 210.032388824969530106)
-
musl
avec 10 itérations :
write(1, "0.035214247182011604\n", 210.035214247182011604)
musl
est plus lent de 0,0028254222124814987 seconde. La différence augmente avec le nombre d'itérations, je suppose que la différence se situe au niveau de l'allocation mémoire des objets JSON.
Si nous réduisons le point de référence à la seule importation json
nous remarquons que la différence n'est pas si énorme :
$ BENCHMARK="import timeit; print(timeit.timeit('import json;', number=5000))"
$ docker run python:3-slim python -c "$BENCHMARK"
0.03683806210756302
$ docker run python:3-alpine3.6 python -c "$BENCHMARK"
0.038280246779322624
Le chargement des bibliothèques Python semble comparable. Génération de list()
produit une plus grande différence :
$ BENCHMARK="import timeit; print(timeit.timeit('list(range(10000))', number=5000))"
$ docker run python:3-slim python -c "$BENCHMARK"
0.5666235145181417
$ docker run python:3-alpine3.6 python -c "$BENCHMARK"
0.6885563563555479
L'opération la plus coûteuse est évidemment json.dumps()
ce qui pourrait indiquer des différences dans l'allocation de la mémoire entre ces bibliothèques.
En regardant à nouveau la référence , musl
est vraiment légèrement plus lent dans l'allocation de la mémoire :
musl | glibc
-----------------------+--------+--------+
Tiny allocation & free | 0.005 | 0.002 |
-----------------------+--------+--------+
Big allocation & free | 0.027 | 0.016 |
-----------------------+--------+--------+
Je ne suis pas sûr de ce qu'on entend par "grosse allocation", mais musl
est presque 2 fois plus lent, ce qui peut devenir significatif lorsque vous répétez ces opérations des milliers ou des millions de fois.