Dockerコンテナとネイティブ実行の速度比較

##はじめに
[Docker](https://www.docker.com/)はLinux上でのコンテナ型仮想化技術の一つである。ホストOSと同じLinuxカーネルしか動かすことできないという制約はあるが、従来のハイバーバイザー型の仮想化技術に比べて起動が速く性能的なペナルティが少ないと言われる。とは言え、何らかのオーバーヘッドがあるはずで、それをちょっと調べてみた。

##テスト環境
本来であればLinuxマシンの上でホストとDocker上の比較をすべきと思うが、手元には古めのMac(MacBookAir4,2 13-inch Mid 2011)しかなく、それで確認をしてみた。DockerのランタイムはVersion 17.09.0-ce-mac35 (19611)。
ちなみに、MacOSの上でなぜDockerが動くのか？DockerはLinuxカーネルの上でしか動かないんじゃないのか？　それはそうなんだけど、最近のMacOSには[Hypervisor Framework](https://developer.apple.com/documentation/hypervisor)というのが載っていてそこで別のOSを動かせる。Dockerはこの上に[Hyperkit](https://github.com/moby/hyperkit)というツールキットを載せ、その上でLinuxカーネルを動かしている。DockerのコンテナはそのLinux上で動くという構成だ。[ここの記事](http://www.publickey1.jp/blog/16/docker_hyperkit.html)に図入りで簡潔に説明されています。

##比較実験
比較に使ったのは[redis](https://redis.io/)というキー・バリュー型のデータベース。これに[redis-benchmark](https://redis.io/topics/benchmarks)という性能評価のツールが付属しているのでこれを使ってみる。
測ったのは次の3パターン。どれもバージョンは4.0.2。

1. *Native*: MacOSの上で直接動かす　(`brew install redis`でインストール)
2. *Docker*: 一つのDockerコンテナ上で動かす
3. *Docker.Remote*: redisとredis-benchmarkを別のDockerコンテナで動かす

そして1と2に関しては、tcpのport(デフォルトで6379)に接続する方法と、Unix domain socketで接続する方法(すなわちプロセス間通信)の2種類ができるのでそれを試している。便宜上、前者をtcp接続、後者をunix接続と呼ぶことにする

##実験結果
一覧の表にしてみた。

|   |Native(tcp)|Native(unix)|Docker(tcp)|Docker(unix)|Docker.remote|
|---|---|---|---|---|---|
|PING_INLINE|24384.3|77942.32|48899.75|81900.09|36469.73|
|PING_BULK|26068.82|88105.73|48638.13|85910.65|37133.31|
|SET|26567.48|88261.25|52002.08|90415.91|37792.89|
|GET|27300.03|83612.04|48100.05|82576.38|37411.15|
|INCR|27685.49|79554.5|51519.84|88028.16|38431.98|
|LPUSH|27329.87|86132.64|52029.14|97181.73|36968.58|
|RPUSH|20404.0|86355.79|54112.55|88652.48|39385.59|
|LPOP|22456.77|85034.02|52603.89|93632.96|39968.02|
|RPOP|24746.35|79681.27|52994.17|85543.2|36818.85|
|SADD|24324.98|94428.7|49504.95|91491.3|33156.5|
|HSET|23866.35|93545.37|53648.07|95057.03|36778.23|
|SPOP|24734.11|90497.73|47801.15|91996.32|32541.49|
|LPUSH(neededtobenchmarkLRANGE)|24336.82|94786.73|53734.55|90826.52|38417.21|
|LRANGE_100(first100elements)|9061.25|15581.18|28785.26|38328.86|22655.19|
|LRANGE_300(first300elements)|4830.68|6147.42|12247.4|14836.8|8915.04|
|LRANGE_500(first450elements)|3478.38|4192.34|9373.83|10488.78|7043.74|
|LRANGE_600(first600elements)|2917.66|3113.81|7452.68|7990.41|5470.76|
|MSET(10keys)|21294.72|51679.59|55928.41|98328.42|36036.04|

でもこれだとちょっとわかりにくいので、[pandas](https://pandas.pydata.org/)と[matplotlib](https://matplotlib.org/)を使ってグラフにしてみた。

###tcp接続とunix接続の比較
MacOS NativeでもDockerの上でもUnix接続のほうが断然早い。まあこれはTCPのオーバーヘッドを考えれば当然の結果。
![fig0.png](https://qiita-image-store.s3.amazonaws.com/0/30342/a768f46f-2354-f32b-6a5a-b376520b3eb2.png "fig0.png")
![fig3.png](https://qiita-image-store.s3.amazonaws.com/0/30342/19092eb9-2d39-a4ee-8390-e9658c501cd2.png "fig3.png")

###MacOS NativeとDockerの比較
これが本題の比較なのだが、ちょっと意外な結果。

![fig1.png](https://qiita-image-store.s3.amazonaws.com/0/30342/141d3fd9-2612-d936-4b58-be6bd933c242.png "fig1.png")
![fig2.png](https://qiita-image-store.s3.amazonaws.com/0/30342/2ca99ced-2ba5-38dc-97cf-6ad14051823e.png "fig2.png")

Unix Socket接続の方はドッコイドッコイだが(これも少し差が付くと思っていたので意外は意外)、TCP接続の方はなぜかDockerの方が早い。何回かやってみるとやる度にそれなりに値が振れるのだが、傾向としてDockerの方が早そうというのは変わらない。コレなんでだろうな...

###Dockerのコンテナを別にした時との比較
通常Dockerでは「1サービス1コンテナ」というポリシーで運用するのでそれに従うとRedisを稼働させるコンテナとそれを使うロジックを実装するコンテナは別になる。その構成だとTCP接続しか出来ないのだが、それを1コンテナ構成で一緒にした時との比較をしてみるとこんな感じ。
![fig4.png](https://qiita-image-store.s3.amazonaws.com/0/30342/c09cc0c4-c84f-c897-c4d7-55b2e78ad401.png "fig4.png")


この差が大きいか小さいかは用途次第だけど、確かに差はあることがわかる。

##まとめ
予想外の結果が出ていてちょっと戸惑っているが、一つ目の結論は

* Linuxマシンの上で比較してみたい。

ということ。

そして、例えばRedisを一つのクライアントからだけ使うなら

* 同じコンテナに入れてUnix domainのソケット接続をする

のも性能を稼ぎたい場合はありなのかな。

そして今回の実験につかったソース他は[こちら](https://github.com/ksato9700/redis-benchmark-comparison)。