J'ai exécuté 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
entraînant 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) du projet musl ( URL miroir ). Il existe de nombreuses différences entre ces bibliothèques . Par conséquent, chaque bibliothèque peut être plus performante dans certains cas d'utilisation.
Voici une différence entre ces commandes ci-dessus . La sortie commence à différer de la ligne 269. Bien sûr, il y a différentes adresses en mémoire, mais sinon c'est très similaire. La plupart du temps est évidemment passé à attendre la fin de la python
commande.
Après l'installation 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 principales pourrait contribuer à de meilleures performances. Du diff, 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 pour optimiser les performances de la boucle. Je ne suis pas suffisamment qualifié pour juger si le problème de performances est causé par 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 secondes. Comme la différence augmente avec le nombre d'itérations, je suppose que la différence réside dans l'allocation de mémoire des objets JSON.
Si nous réduisons la référence à l'importation uniquement, 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érer list()
produit une différence plus importante:
$ 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
Évidemment, l'opération la plus coûteuse est json.dumps()
, ce qui pourrait indiquer des différences d'allocation de mémoire entre ces bibliothèques.
En regardant à nouveau le benchmark , l'
musl
allocation de mémoire est vraiment légèrement plus lente:
musl | glibc
-----------------------+--------+--------+
Tiny allocation & free | 0.005 | 0.002 |
-----------------------+--------+--------+
Big allocation & free | 0.027 | 0.016 |
-----------------------+--------+--------+
Je ne sais pas ce que l'on entend par "grosse allocation", mais elle musl
est presque 2 fois plus lente, ce qui peut devenir significatif lorsque vous répétez de telles opérations des milliers ou des millions de fois.