言語の性能の差がApacheモジュールの決定的な差でないことを教えてやる

というツイートに触発されて、mod_luaのLuaJIT版の速度がどの程度早く、mod_mrubyと比較してどれほどの性能差があるのかを試してみました。

mod_luaのLuaJIT版の設定

ここが結構はまってしまって、最新のApache2.4.3ソースで–enable-luajitとしても、LuaJIT版でmod_luaが動作するようにはなっていません。バグかな?

というわけで、Apacheのソースを追って無理やりmod_luaをLuaJIT版に対応させました。まずは、普通に以下のようにconfigureを実行します。

[program lang=’bash’ escaped=’true’]

./configure --prefix=/usr/local/apache243 --with-apr=/usr/local/apr --with-apr-util=/usr/local/apr --enable-modules=all --enable-mods-shared=all --enable-mpms-shared='prefork worker event' --enable-lua --enable-luajit

[/program]

これだけではLuaJIT版できちんと動作しないので、以下のように修正します。

 build/config_vars.mkを弄る

以下のように変更します。

[program lang=’bash’ escaped=’true’]

(snip)
MOD_LUA_LDADD = -lluajit-5.1 -lm -ldl
(snip)
EXTRA_CPPFLAGS = -DLINUX=2 -D_REENTRANT -D_GNU_SOURCE -D_LARGEFILE64_SOURCE
(snip)

[/program]

やったこととしては、LuaJITのライブラリの指定とlibdlを指定すること、EXTRA_CPFLAGSに書かれていた-DAP_ENABLE_LUAJITを削除することです。これでmakeすれば、LuaJIT版mod_luaで動作します。

mod_luaのLuaJIT版がいかに早いか

LuaJIT版のmod_luaの速度を試してみましょう。

mod_luaの通常版と、mod_luaのLuaJIT版、そして、mod_mrubyそれぞれにフィボナッチ数列の計算をさせてみました。コードは以下です。

luaのコード

[program lang=’lua’ escaped=’true’]

require "apache2"

function fib(n)
  if n < 2 then
    return n
  end
  return fib(n-2) + fib(n-1)
end

function handle(r)
    r:puts(fib(37))
    return 200
end

[/program]

mrubyのコード

[program lang=’ruby’ escaped=’true’]

def fib(n)
    return n if (n < 2)
    return fib(n - 2) + fib(n - 1)
end

Apache.rputs fib(37).to_s

[/program]

これらに対して、curlとtimeコマンドで処理速度を比較すると以下のようになりました。ちなみに、Apacheモジュール版ではなく、lua、luajit、mruby、rubyコマンドで上記のコード(r:putsやApache.rputsを標準のputsにしたもの)を実行した時の処理時間も参考のため載せておきます。

mod_lua

(通常)

mod_lua

(LuaJIT)

mod_mruby

(最新)

lua

(5.1.4 )

luajit

(1.1.8)

mruby

(最新)

ruby

(1.9.3p327)

処理時間(real time)(秒) 11.346 0.514 10.144 11.614 2.190 10.664 7.494

このようになりました。

まずは、LuaJITが早すぎですね。これはびっくりしました。しかし、mod_lua(LuaJIT版)がluajitコマンドより4倍程早いのはなぜでしょう。また、mrubyがLuaよりも少しだけ早い事がわかりました。しかし、Ruby1.9.3の方が早いですね。

言語の性能の差がApacheモジュールの決定的な差でないことを教えてやる

というわけで、ここからが本題です。

現状、LuaJIT版のmod_luaには明らかに言語の処理速度では勝てなさそうに思えます。しかし、「この言語の性能の差がApacheモジュールの決定的な差でないことを教えてやる」というわけでいつものhello worldを出力するベンチマーク勝負を行なってみました。なぜいつもスクリプトの処理をできるだけ軽量にするかというと、言語による処理速度を最低限にすることで、Apacheモジュールとしてのアーキテクチャの差を最大限に出して比較をしたかったためです。

実際に、hello worldを出力するluaとmrubyのそれぞれのスクリプトに対して、同時接続数100総接続数10万でベンチを行うと、以下のようになりました。Apacheのバージョンは2.4.3のevent MPMで行いました。

mod_lua(通常) mod_lua(LuaJIT版) mod_mruby
リクエスト処理数/sec  5758.86  6429.34  13442.97

ということで、Apacheモジュールのアーキテクチャの性能比較を行った場合は、このようにmod_mrubyの方がかなり早い事がわかります。まさにこれは。

言語の性能の差がApacheモジュールの決定的な差でないことを教えてやる!

ということですね。(少しだけ安心しました。これでLuaJIT版に惨敗していたら一から勉強し直しだなぁ、なんてどきどきしながらベンチをとっていました。)

どうしてこのような差がでるのかというと、これまでブログを読んでいる方は大体分かっていると思いますが、mod_luaがリクエスト毎にステートマシンを生成して破棄しているのに対し、mod_mrubyはサーバ起動時にステートマシンを生成して、複数のリクエストでそのステートマシンを使いまわしているためです。

まとめ

以上のように、 フィボナッチ数列の計算速度では負けてしまいましたが、Apacheモジュールのアーキテクチャの性能比較としてはmod_mrubyに軍配が上がるのではないかと思われます。

しかし、フィボナッチ数列の計算のような処理等、処理に時間のかかるような実装を記述する場合は、mod_luaのLuaJIT版がその性能を発揮するでしょう。一方で、そこまで処理に時間のかからないような実装を記述する場合は、Apacheモジュールとしてのアーキテクチャの優位性によって、mod_mrubyの方が性能を発揮するように思われます。

これらをまとめると、

  • コードの処理がボトルネックにならない程度の実装の場合はmod_mrubyを使う方が効率が良い

ということになるでしょう。

ちなみに、上記のツイートだけでなく以下のようなツイートにも勇気付けられました。