54 votes

Pourquoi l'image Docker Alpine est-elle plus de 50 % plus lente que l'image Ubuntu ?

J'ai remarqué que mon application Python est beaucoup plus lent lorsqu'il fonctionne sur python:2-alpine3.6 que de l'exécuter sans Docker sur Ubuntu. J'ai créé deux petites commandes de référence et une énorme différence est visible entre les deux systèmes d'exploitation, tant lorsque je les exécute sur un serveur Ubuntu que lorsque j'utilise Docker pour Mac.

$ BENCHMARK="import timeit; print(timeit.timeit('import json; json.dumps(list(range(10000)))', number=5000))"
$ docker run python:2-alpine3.6 python -c $BENCHMARK
7.6094589233
$ docker run python:2-slim python -c $BENCHMARK
4.3410820961
$ docker run python:3-alpine3.6 python -c $BENCHMARK
7.0276606959
$ docker run python:3-slim python -c $BENCHMARK
5.6621271420

J'ai également essayé le "benchmark" suivant, qui n'utilise pas Python :

$ docker run -ti ubuntu bash
root@6b633e9197cc:/# time $(i=0; while (( i < 9999999 )); do (( i ++
)); done)

real    0m39.053s
user    0m39.050s
sys     0m0.000s
$ docker run -ti alpine sh
/ # apk add --no-cache bash > /dev/null
/ # bash
bash-4.3# time $(i=0; while (( i < 9999999 )); do (( i ++ )); done)

real    1m4.277s
user    1m4.290s
sys     0m0.000s

Quelle pourrait être la cause de cette différence ?

67voto

Brian Harris Points 1031

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.

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X