it-swarm-ja.com

Alpine Dockerの画像がUbuntuの画像よりも50%以上遅くなるのはなぜですか?

私のPythonアプリケーションはpython:2-Alpine3.6上で実行するとUbuntu上でDockerなしで実行するよりも遅く遅くなることに気づきました。私は2つの小さなベンチマークコマンドを思い付きました、そして私がUbuntuサーバでそれらを実行している時と、私がDocker for Macを使っている時の両方で、2つのオペレーティングシステムの間に目に見える大きな違いがあります。

$ 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
[email protected]:/# 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

この違いの原因は何でしょうか。

34
Underyx

私はあなたが行ったのと同じベンチマークを実行しました。Python3だけを使います。

$ 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

アルパインは、 muslプロジェクトとは異なるlibc(基本システムライブラリ)の実装を使用しています。ミラーURL )。これらのライブラリの間には 多くの違いがあります 。その結果、特定のユースケースでは各ライブラリのパフォーマンスが向上する可能性があります。

これが上記のコマンド間の strace diffです 。出力は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操作の数を減らすと、パフォーマンスが向上する可能性があります。 diffから、まったく同じPythonコードを実行すると、わずかに異なるシステムコールが発生する可能性があることがわかります。おそらく最も重要なことは、ループのパフォーマンスを最適化することにあります。パフォーマンスの問題がメモリ割り当てによるのか、それとも他の命令によるのかを判断するのに十分な資格がありません。

  • 10回の反復を含むglibc

    write(1, "0.032388824969530106\n", 210.032388824969530106)
    
  • 10回の反復を含むmusl

    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倍遅いので、このような操作を数千から数百万回繰り返すと重大になる可能性があります。

42
Tombart