Ho eseguito lo stesso benchmark di te, usando solo 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
con conseguente differenza di più di 2 secondi:
$ docker run python:3-slim python -c "$BENCHMARK"
3.6475560404360294
$ docker run python:3-alpine3.6 python -c "$BENCHMARK"
5.834922112524509
Alpine sta utilizzando un'implementazione diversa di libc
(libreria del sistema di base) dal progetto musl ( URL mirror ). Ci sono molte differenze tra queste librerie . Di conseguenza, ogni libreria potrebbe funzionare meglio in alcuni casi d'uso.
Ecco una differenza diff tra questi comandi sopra . L'output inizia a differire dalla riga 269. Naturalmente ci sono diversi indirizzi in memoria, ma per il resto è molto simile. La maggior parte del tempo è ovviamente trascorso in attesa che il python
comando finisca.
Dopo l'installazione strace
in entrambi i contenitori, possiamo ottenere una traccia più interessante (ho ridotto il numero di iterazioni nel benchmark a 10).
Ad esempio, glibc
sta caricando le librerie nel modo seguente (riga 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
Lo stesso codice in 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
Non sto dicendo che questa sia la differenza fondamentale, ma ridurre il numero di operazioni I / O nelle librerie core potrebbe contribuire a migliorare le prestazioni. Dal diff si può vedere che l'esecuzione dello stesso codice Python potrebbe portare a chiamate di sistema leggermente diverse. Probabilmente il più importante potrebbe essere fatto nell'ottimizzazione delle prestazioni del loop. Non sono abbastanza qualificato per giudicare se il problema di prestazioni è causato dall'allocazione di memoria o da qualche altra istruzione.
glibc
con 10 iterazioni:
write(1, "0.032388824969530106\n", 210.032388824969530106)
musl
con 10 iterazioni:
write(1, "0.035214247182011604\n", 210.035214247182011604)
musl
è più lento di 0,0028254222124814987 secondi. Man mano che la differenza aumenta con il numero di iterazioni, suppongo che la differenza sia nell'allocazione di memoria degli oggetti JSON.
Se riduciamo il benchmark all'importazione unica json
, notiamo che la differenza non è così grande:
$ 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
Il caricamento delle librerie Python sembra comparabile. La generazione list()
produce una differenza maggiore:
$ 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
Ovviamente è l'operazione più costosa json.dumps()
, che potrebbe indicare differenze nell'allocazione di memoria tra quelle librerie.
Guardando di nuovo al benchmark ,
musl
è davvero leggermente più lenta nell'allocazione della memoria:
musl | glibc
-----------------------+--------+--------+
Tiny allocation & free | 0.005 | 0.002 |
-----------------------+--------+--------+
Big allocation & free | 0.027 | 0.016 |
-----------------------+--------+--------+
Non sono sicuro di cosa si intenda per "grande allocazione", ma musl
è quasi 2 volte più lento, il che potrebbe diventare significativo quando si ripetono tali operazioni migliaia o milioni di volte.