皆さん、Vert.x 使っていますか?
前回の「Vert.x がいいね!(第2回:開発環境を構築する) - Taste of Tech Topics」に続き、今回はVert.xの肝であるEvent LoopsとVerticle Instancesについて書きます。
目次は以下の通りです。
- Vert.x's Threads
- Event Loops
- Verticle Instances
- Event Loops & Verticle Instances
- Performance Test
1. Vert.x's Threads
まずはVert.xのスレッドがどのようになっているのか確認してみましょう。前回も利用したHelloWorldVerticleを利用して、リモートデバッグによってbreak pointで停止させ、動作しているスレッドを見てみます。
図の通り、main threadが1個、vert.x-eventloop-threadが0〜3の4個、vert.x-worker-threadが0〜19の20個が動作していることが分かります。
今回はeventloop-threadに注目したい訳ですが、デフォルトで4個のeventloop-threadが動作しています。ここがEvent Loopが1つのnode.jsとVert.xが異なる点ですね。
2. Event Loops
さて、ではなぜeventloop-threadは4個なのでしょうか。これはVert.xのソースを確認すると分かります。見るべきソースはVertxExecutorFactory.javaです。
public static EventLoopGroup eventLoopGroup(String poolName) { int poolSize = Integer.getInteger("vertx.pool.eventloop.size", Runtime.getRuntime().availableProcessors()); return new NioEventLoopGroup(poolSize, new VertxThreadFactory(poolName)); }
Runtime.getRuntime().availableProcessors()
とある通り、Vert.xはCPUのプロセッサ数を判別して、その環境にあったeventloop-threadの数になるように実装されています。
実際に私の環境で以下のように確認してみるとcpu.thread_countが4なので、2coreのハイパースレッディングで4core相当に見えるために、eventloop-threadも4個になっているようです。
ken@my-vertx-module $ sysctl -a machdep.cpu (略) machdep.cpu.core_count: 2 machdep.cpu.thread_count: 4
3. Verticle Instances
一方で、公式サイトのドキュメントを見ると、vertxコマンドでVerticleやModuleを実行する際に、「-instances」オプションを指定することで、VertilcleやModuleのインスタンス数を指定することができ、コア数に応じてアプリケーションをスケールすることができるぜ!と書かれています。
ここは試しに何も考えず、「-instances 10」と指定してvertxコマンドを実行してみましょう。
ken@my-vertx-module $ vertx runmod com.mycompany~my-module~0.0.1 -cp bin -instances 10 Verticle start. Verticle start. Verticle start. Verticle start. Verticle start. Verticle start. Verticle start. Verticle start. Verticle start. Verticle start.
ご覧の通り、10個のHelloWorldVerticleのインスタンスが生成され、動作していることが分かります。これも実際にVert.xのソースを確認すると分かります。見るべきソースはDefaultPlatformManager.javaです。
for (int i = 0; i < instances; i++) { // Launch the verticle instance Runnable runner = new Runnable() { public void run() { Verticle verticle; try { verticle = verticleFactory.createVerticle(main); } catch (Throwable t) { handleDeployFailure(t, deploymentID, aggHandler); return; } try { addVerticle(deployment, verticle, verticleFactory, modID, main); setPathResolver(modID, modDir); DefaultFutureResult<Void> vr = new DefaultFutureResult<>(); verticle.start(vr); vr.setHandler(new Handler<AsyncResult<Void>>() { @Override public void handle(AsyncResult<Void> ar) { if (ar.succeeded()) { aggHandler.complete(); } else { handleDeployFailure(ar.cause(), deploymentID, aggHandler); } } }); } catch (Throwable t) { handleDeployFailure(t, deploymentID, aggHandler); } } }; if (worker) { vertx.startInBackground(runner, multiThreaded); } else { vertx.startOnEventLoop(runner); } }
この他、関連する以下のソースを見るとだいたいの動作が分かります。
簡単に書くと、Verticleを生成する際に、EventLoopGroupから1つのeventloop-threadを取得し、対象のVerticleに対応づけて、その組み合わせを保持しています。この処理によって、1つのVerticleインスタンスは最初に対応づけられたeventloop-threadからしか呼ばれないことが保証されます。
Verticleを開発する場合に、他のスレッドから呼ばれることがないため、同期や排他などを考えることなく、シンプルなコードを書くことができる、というVert.xの特長はこのように実現されています。
4. Event Loops & Verticle Instances
ここまでの内容から、Event LoopsとVerticle Instancesの関係について整理してみましょう。
- Event Loopsは環境のコア数によってスレッド数が決まる
- Vertcile Instancesは「-instances」オプションの指定によってインスタンス数が決まる
- 1つのVerticleインスタンスが生成される際に、Event Loopsから1つのeventloop-threadが割り当てられる
例えば、4個のeventloop-threadに対して、10個のVerticleインスタンスを生成した場合、
- vertx-eventloop-thread-0
- Vertcile-0
- Vertcile-4
- Vertcile-8
- vertx-eventloop-thread-1
- Vertcile-1
- Vertcile-5
- Vertcile-9
- vertx-eventloop-thread-2
- Vertcile-2
- Vertcile-6
- vertx-eventloop-thread-3
- Vertcile-3
- Vertcile-7
このような割り当てとなります(あくまで例です)。
ここで考えてみましょう。
Verticleを複数インスタンス生成して処理をスケールさせたいとしても、実際の所はEvent Loopsのスレッド数以上にはマルチに処理できないため、Event Loopsのスレッド数以上のVerticle Instancesを指定しても効果がないと考えられます。
5. Performance Test
そこで実際に性能計測を行ってみましょう。私の環境ではeventloop-threadは4個なので、Verticleのインスタンス数を1、2、4、8と変化させた場合の処理性能を計測し、その違いを確認したいと思います。(注意:この計測はあくまでも私の環境で、各条件での処理性能を相対的に確認するものであり、Vert.xの性能値を評価するものではありません)
5.1 事前準備
測定に使うのは、再三登場しているHelloWorldVerticleです。クライアントからの要求をシミュレートするために、Http Performance exampleのClientを利用します。
また、OSのチューニングとして、maxfiles、somaxconnを適当な大きさに設定しています。
ken@my-vertx-module $ launchctl limit (略) maxfiles 65535 65535 ken@my-vertx-module $ sysctl -a | grep somax kern.ipc.somaxconn: 2048
5.2 検証手順
まずはサーバを起動します。
ken@my-vertx-module $ vertx runmod com.mycompany~my-module~0.0.1 -cp bin (-instances 2/4/8)
クライアントは別のターミナルを2つ開いて、それぞれで以下を実行します。
ken@java $ vertx run perf/RateCounter.java -cluster Starting clustering... No cluster-host specified so using address 10.0.1.6
ken@java $ vertx run httpperf/PerfClient.java -instances 4 -cluster Starting clustering... No cluster-host specified so using address 10.0.1.6
負荷をかけるため、クライアント側も「-instances 4」を指定してマルチコアを使うようにしています。
PerfClientは1インスタンス辺り100接続をするため、計400接続を同時に行って、リクエストを送信します。
5.3 測定結果
5.3.1 Verticleインスタンス数1(「-instances」指定なし)
71968 Rate: count/sec: 124666.66666666667 Average rate: 120609.15962650067 74968 Rate: count/sec: 124666.66666666667 Average rate: 120771.52918578594 77968 Rate: count/sec: 123333.33333333333 Average rate: 120870.10055407346 80967 Rate: count/sec: 125375.12504168056 Average rate: 121036.96567737473 83967 Rate: count/sec: 123333.33333333333 Average rate: 121119.01104005145 86967 Rate: count/sec: 123333.33333333333 Average rate: 121195.39595478745 89967 Rate: count/sec: 126000.0 Average rate: 121355.60816743918 92967 Rate: count/sec: 126000.0 Average rate: 121505.48043929566 95968 Rate: count/sec: 123958.68043985339 Average rate: 121582.19406468823 98968 Rate: count/sec: 125333.33333333333 Average rate: 121695.9017056018
5.3.2 Verticleインスタンス数2(「-instances 2」)
72824 Rate: count/sec: 179393.13104368123 Average rate: 168241.23915192793 75825 Rate: count/sec: 176607.79740086637 Average rate: 168572.37059017475 78825 Rate: count/sec: 178666.66666666666 Average rate: 168956.54931810973 81825 Rate: count/sec: 176000.0 Average rate: 169214.78765658417 84825 Rate: count/sec: 178000.0 Average rate: 169525.49366342468 87825 Rate: count/sec: 172000.0 Average rate: 169610.01992598918 90825 Rate: count/sec: 171333.33333333334 Average rate: 169666.94192127718 93825 Rate: count/sec: 175333.33333333334 Average rate: 169848.12150279776 96825 Rate: count/sec: 178000.0 Average rate: 170100.69713400464 99825 Rate: count/sec: 176666.66666666666 Average rate: 170298.02153769095
5.3.3 Verticleインスタンス数4(「-instances 4」)
72069 Rate: count/sec: 189270.24325224926 Average rate: 173805.6584661921 75068 Rate: count/sec: 190730.24341447148 Average rate: 174481.8031651303 78068 Rate: count/sec: 190000.0 Average rate: 175078.13700876158 81068 Rate: count/sec: 189333.33333333334 Average rate: 175605.66438052006 84069 Rate: count/sec: 191936.02132622458 Average rate: 176188.60697760174 87068 Rate: count/sec: 190063.3544514838 Average rate: 176666.51352965497 90068 Rate: count/sec: 188666.66666666666 Average rate: 177066.21663631924 93069 Rate: count/sec: 193268.91036321226 Average rate: 177588.6707711483 96068 Rate: count/sec: 193397.79926642214 Average rate: 178082.19178082192 99068 Rate: count/sec: 192666.66666666666 Average rate: 178523.84220939153
5.3.4 Verticleインスタンス数8(「-instances 8」)
70314 Rate: count/sec: 185333.33333333334 Average rate: 161646.32932275222 73315 Rate: count/sec: 186604.4651782739 Average rate: 162667.93971220078 76314 Rate: count/sec: 186728.90963654552 Average rate: 163613.4916267002 79313 Rate: count/sec: 187395.79859953318 Average rate: 164512.7532686949 82314 Rate: count/sec: 184605.1316227924 Average rate: 165245.28026824113 85313 Rate: count/sec: 186728.90963654552 Average rate: 166000.492304807 88314 Rate: count/sec: 186604.4651782739 Average rate: 166700.63636569513 91315 Rate: count/sec: 177940.68643785405 Average rate: 167070.0323057548 94314 Rate: count/sec: 179393.13104368123 Average rate: 167461.882647327 97314 Rate: count/sec: 178666.66666666666 Average rate: 167807.3041905584
5.4 まとめ
- (1)Verticleインスタンスが1の場合は、約120K count/sec
- (2)Verticleインスタンスが2の場合は、約170K count/sec
- (3)Verticleインスタンスが4の場合は、約190K count/sec
- (4)Verticleインスタンスが8の場合は、約180K count/sec
(1)に比べて、(2)では2つのeventloop-threadで処理しているので、処理性能が向上しています。
(2)と(3)では(3)の方が処理性能は確かに向上しているものの、増分は想定よりも低くなっています。
ただし、これは(2)の時点でCPU使用率が既に80〜90%近くになっており、これ以上性能を出せない状況にあることが考えられます。
(4)は(3)に比べて、処理性能が下がっています。
Verticleのインスタンス数がeventloop-threadの数よりも大きいことが逆に処理性能を悪化させているのか、マシンリソースの問題かは分かりませんが、より性能が向上しないことは分かりました。
と言うわけで、Event LoopsとVerticle Instancesの関係について確認してきました。
Vert.xによるアプリケーションの開発で処理性能を引き出したい場合には、Event Loopの数と、Vertcileのインスタンス数について、ここまで見てきた関係を頭に入れながら開発することが重要となります。
次回は?
Vert.xのテストについてその手法を確立したいので、次回はその辺りを取り上げたいと思います。