Ejecuté el mismo punto de referencia que usted, 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
resultando en más de 2 segundos de diferencia:
$ docker run python:3-slim python -c "$BENCHMARK"
3.6475560404360294
$ docker run python:3-alpine3.6 python -c "$BENCHMARK"
5.834922112524509
Alpine está utilizando una implementación diferente de libc
(biblioteca del sistema base) del proyecto musl ( URL espejo ). Hay muchas diferencias entre esas bibliotecas . Como resultado, cada biblioteca podría funcionar mejor en ciertos casos de uso.
Aquí hay una diferencia entre los comandos anteriores . La salida comienza a diferir de la línea 269. Por supuesto, hay diferentes direcciones en la memoria, pero por lo demás es muy similar. Obviamente, la mayor parte del tiempo se pasa esperando python
a que termine el comando.
Después de instalar strace
en ambos contenedores, podemos obtener una traza más interesante (he reducido el número de iteraciones en el punto de referencia a 10).
Por ejemplo, glibc
está cargando bibliotecas de la siguiente manera (línea 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
El mismo código en 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
No digo que esta sea la diferencia clave, pero reducir la cantidad de operaciones de E / S en las bibliotecas principales podría contribuir a un mejor rendimiento. Desde el diff puede ver que ejecutar el mismo código de Python puede conducir a llamadas de sistema ligeramente diferentes. Probablemente lo más importante se podría hacer para optimizar el rendimiento del bucle. No estoy lo suficientemente calificado para juzgar si el problema de rendimiento es causado por la asignación de memoria o alguna otra instrucción.
glibc
con 10 iteraciones:
write(1, "0.032388824969530106\n", 210.032388824969530106)
musl
con 10 iteraciones:
write(1, "0.035214247182011604\n", 210.035214247182011604)
musl
es más lento en 0.0028254222124814987 segundos. A medida que la diferencia aumenta con el número de iteraciones, supongo que la diferencia está en la asignación de memoria de los objetos JSON.
Si reducimos el punto de referencia a solo importar json
, notamos que la diferencia no es tan 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
La carga de las bibliotecas de Python parece comparable. Generar list()
produce una mayor diferencia:
$ 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
Obviamente, la operación más costosa es la json.dumps()
que podría señalar diferencias en la asignación de memoria entre esas bibliotecas.
Mirando nuevamente al punto de referencia , la
musl
asignación de memoria es realmente un poco más lenta:
musl | glibc
-----------------------+--------+--------+
Tiny allocation & free | 0.005 | 0.002 |
-----------------------+--------+--------+
Big allocation & free | 0.027 | 0.016 |
-----------------------+--------+--------+
No estoy seguro de lo que se entiende por "gran asignación", pero musl
es casi 2 veces más lento, lo que puede llegar a ser significativo cuando repite tales operaciones miles o millones de veces.