Я выполнил тот же тест, что и вы, используя только 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
в результате разница более 2 секунд:
$ docker run python:3-slim python -c "$BENCHMARK" 3.6475560404360294 $ docker run python:3-alpine3.6 python -c "$BENCHMARK" 5.834922112524509
Alpine использует отличную реализацию libc
(базовую системную библиотеку) от проекта musl . Есть много различий между этими библиотеками . В результате каждая библиотека может работать лучше в определенных случаях использования.
Вот разница между этими командами выше . Вывод начинает отличаться от строки 269. Конечно, в памяти есть разные адреса, но в остальном он очень похож. Большая часть времени, очевидно, тратится на ожидание python
команды.
После установки strace
в оба контейнера мы можем получить более интересную трассировку (я сократил количество итераций в тесте до 10).
Например, glibc
загружает библиотеки следующим образом (строка 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
тот же код в 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
Я не говорю, что это ключевое отличие, но уменьшение количества операций ввода-вывода в основных библиотеках может повысить производительность. Из сравнения видно, что выполнение одного и того же кода Python может привести к немного другим системным вызовам. Вероятно, самое важное можно сделать при оптимизации производительности цикла. Я недостаточно квалифицирован, чтобы судить, вызвана ли проблема производительности выделением памяти или какой-то другой инструкцией.
glibc
с 10 итерациями:write(1, "0.032388824969530106\n", 210.032388824969530106)
musl
с 10 итерациями:write(1, "0.035214247182011604\n", 210.035214247182011604)
musl
медленнее на 0,0028254222124814987 секунд. Поскольку различие увеличивается с увеличением числа итераций, я предполагаю, что разница заключается в распределении памяти для объектов JSON.
Если мы сократим эталонный тест исключительно до импорта, json
мы заметим, что разница не так уж велика:
$ 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
Загрузка библиотек python выглядит сопоставимо. Генерация list()
производит большую разницу:
$ 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
Очевидно, что наиболее дорогой является операция json.dumps()
, которая может указывать на различия в распределении памяти между этими библиотеками.
Еще раз посмотрев на эталонный тест, musl
мы немного медленнее выделяем память:
musl | glibc -----------------------+--------+--------+ Tiny allocation & free | 0.005 | 0.002 | -----------------------+--------+--------+ Big allocation & free | 0.027 | 0.016 | -----------------------+--------+--------+
Я не уверен, что подразумевается под «большим распределением», но musl
почти в 2 раза медленнее, что может стать значительным, если вы повторяете такие операции тысячи или миллионы раз.