Почему изображение альпийского Docker более чем на 50% медленнее, чем изображение Ubuntu?

8326
Underyx

Я заметил, что мое приложение на Python работает намного медленнее, python:2-alpine3.6чем без Docker в Ubuntu. Я разработал две небольшие команды для тестирования производительности, и между двумя операционными системами заметна огромная разница, как при запуске их на сервере Ubuntu, так и при использовании Docker для 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 

Я также попробовал следующий «тест», который не использует 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 

Что может быть причиной этой разницы?

22
@ Смотри еще раз: отсчет времени начинается после установки bash, внутри запущенной оболочки bash Underyx 6 лет назад 1

1 ответ на вопрос

27
Tombart

Я выполнил тот же тест, что и вы, используя только 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 раза медленнее, что может стать значительным, если вы повторяете такие операции тысячи или миллионы раз.

Всего несколько исправлений. musl - это не _own_ реализация Alpine glibc. 1st musl не является (пере) реализацией glibc, но отличается реализацией ** libc ** в соответствии со стандартом POSIX. 2nd musl - это не «собственная» вещь Alpine, это отдельный, не связанный проект, и musl не используется только в Alpine. Jakub Jirutka 6 лет назад 5
@JakubJirutka спасибо за исправления Tombart 6 лет назад 1
учитывая, что musl libc выглядит лучше на основе стандартов *, не говоря уже о более новой реализации, почему в этих случаях он проигрывает glibc? * Ср. https://wiki.musl-libc.org/functional-differences-from-glibc.html Forest 5 лет назад 0