Я запустив той самий тест, що і ви, використовуючи лише 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 ( дзеркальна URL-адреса ). Існує багато відмінностей між цими бібліотеками . Як результат, у певних випадках кожна бібліотека може працювати краще.
Ось ця різниця між цими командами вище . Вихід починає відрізнятися від рядка 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 × повільніше, що може стати значущим при повторенні таких операцій тисячі чи мільйони разів.