为什么Alpine Docker映像比Ubuntu映像慢50%?


35

我发现我的Python应用程序是慢上运行时,它python:2-alpine3.6不是在Ubuntu上运行它没有泊坞窗。我想出了两个小的基准命令,并且在我在Ubuntu服务器上运行它们以及在Mac上使用Docker时,两个操作系统之间都存在巨大的差异。

$ 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

是什么导致这种差异?


1
@Seth再次查看:启动bash外壳后,bash安装后开始计时
Underyx

Answers:


45

我使用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正在使用libcmusl项目镜像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

我并不是说这是关键的区别,但是减少核心库中的I / O操作数量可能有助于提高性能。从差异中可以看到,执行完全相同的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倍,当您重复执行数千或数百万次此类操作时,这可能会变得很重要。


12
只需进行一些更正。musl不是Alpine 自己的glibc实现。第一个问题不是glibc的(重新)实现,而是每个POSIX标准的libc的不同实现。第二音乐不是Alpine 自己的东西,它是一个独立的,无关的项目,而且Musl不仅在Alpine中使用。
Jakub Jirutka

鉴于musl libc似乎是一个更好的基于标准的*,更不用说更新的实现了,为什么在这些情况下它似乎不如glibc好?* cf。wiki.musl-libc.org/functional-differences-from-glibc.html
森林

0.0028秒的差异是否具有统计学意义?相对偏差仅为0.0013%,您要采集10个样本。那十次运行的(估计)标准偏差是多少(甚至最大-最小差异)?
Peter Mortensen

@PeterMortensen有关基准测试结果的问题,请参阅Eta Labs代码:etalabs.net/libc-bench.html例如,malloc压力测试重复了10万次。结果可能在很大程度上取决于库版本,GCC版本和所使用的CPU,仅举几个方面。
Tombart
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.