Luaのメソッドやクラスやrequireなど

bench.luaというファイル名のクラスを作り、さらにmymethod.luaという名前のファイルを作り、それをuse.luaで使ってみました。
なかなか面倒でした^^;

動作例

$ lua use.lua 
This is method.
3.7193298339844e-05

bench.lua
local Bench = {}
Bench.__index = Bench
function Bench.new()
	local bench = {}
	setmetatable(bench, Bench)
	bench.start = socket.gettime()
	return bench
end
function Bench:stop()
	return socket.gettime() - self.start
end
return Bench

mymethod.lua
local M = {}

local function getString()
	return "This is method."
end

M.getString = getString
return M

use.lua
local socket = require "socket"
Bench = require "bench"
My = require "mymethod"

b1 = Bench.new()
print(My.getString())
print(b1:stop())

socketライブラリのインストール
$ luarocks install luasocket

Nginx+LuaでのShared Dictに対してjsonのやりとりの速度

Nginx+Luaには簡単なインメモリKVSがついています。shared dictというものです。
単純なKey-Valueであれば素直にこれを使えばよいのですが、Luaのtable、つまり配列のようなものを保存したくなります。

そのときにjsonでencodeして保存して、使うときはデコードすればいいでしょう。しかしパフォーマンスはどうなのかと思って、実験してみました。

結論から言えば、単純なHelloWorldに比べて-15%程度の速度低下が認められますが、致命的ではないように感じました。なので、配列はjsonにしてshared dictに入れておけば良いと思います。永続化したい場合は、ときどきjson形式でファイルに書き込みしておくと良いかもしれません。

以下、コードと操作とwrkベンチマークです。nginxの詳しい使い方は検索して探してください。

nginx.confの抜粋

		# ping
		location /ping {
			default_type text/html;
			content_by_lua 'ngx.say("PONG!")';
		}		
		
		location /json_init {
			default_type text/html;
			content_by_lua '
				json = {math.random(0, 1000000)}
				sdict:set("json", cjson.encode(json))
				ngx.say(cjson.encode(json))
			';
		}
		
		location /json_add {
			default_type text/html;
			content_by_lua '
				json = sdict:get("json")
				if not json then
					ngx.say("FAILURE. No Shared Dict of [json].")
					return
				end
				
				t = cjson.decode(json)
				random = math.random(0, 1000000)
				table.insert(t, random)
				
				encoded = cjson.encode(t)
				sdict:set("json", encoded)
				ngx.say(encoded)
			';
		}
		
		location /json_get {
			default_type text/html;
			content_by_lua '
				json = sdict:get("json")
				if not json then
					ngx.say("FAILURE. No Shared Dict of [json].")
					return
				end
				
				t = cjson.decode(json)
				
				ngx.say(t[#t])
			';
		}
		

操作やパフォーマンス
$ curl localhost:8080/json_init
[794207]

$ curl localhost:8080/json_add
[794207,698853]

$ curl localhost:8080/json_add
[794207,698853,590104]

$ curl localhost:8080/json_get
590104

パフォーマンス
$ ./wrk -c 50 -t 4 -d 3 http://localhost:8080/ping
Running 3s test @ http://localhost:8080/ping
  4 threads and 50 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     4.63ms   12.09ms 104.47ms   96.01%
    Req/Sec     5.40k     3.66k   22.78k    77.00%
  58950 requests in 3.00s, 10.12MB read
Requests/sec:  19659.76
Transfer/sec:      3.37MB

$ ./wrk -c 50 -t 4 -d 3 http://localhost:8080/json_get
Running 3s test @ http://localhost:8080/json_get
  4 threads and 50 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.78ms    8.80ms  93.55ms   98.13%
    Req/Sec     4.61k     1.54k   19.78k    91.59%
  51955 requests in 3.00s, 8.97MB read
Requests/sec:  17330.77
Transfer/sec:      2.99MB

# きちんと出来てるか確認
$ curl http://localhost:8080/json_get
590104


スペックは、
– Mac (10.8)
– MacBook 2009 late
– gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)
– ngx_openresty-1.4.2.9
です。

MacOSXでソースからLua 5.1とLuarocksをインストール

Lua 5.1 (Luaのsocketは5.1対応なので)をインストールしようと思ってmacportsで見てみたら、5.2と5.0しかありませんでした。

ですのでソースからインストールしてみました。

$ cd

# 作業領域を作ります
$ mkdir lua-workspace
$ cd lua-workspace

# lua 5.1.5
$ wget http://www.lua.org/ftp/lua-5.1.5.tar.gz
$ tar zxvf lua-5.1.5.tar.gz 
$ cd lua-5.1.5
$ make macosx
$ sudo make install

$ cd ..

# luarocks
$ wget http://luarocks.org/releases/luarocks-2.1.0.tar.gz
$ tar zxvf luarocks-2.1.0.tar.gz
$ cd luarocks-2.1.0
$ ./configure
$ make build
$ sudo make install 

# 反映させる
$ exec $SHELL

# 確認
$ lua -v
Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio

以上です。

Nginx(OpenResty)でLuaを動かす+パフォーマンス(ついでにHHVMも)

追記@2013年10月24日
MacにOpenRestyをインストールする場合、makeで失敗することがありました。

$ make
~~~ 中略 ~~~
Undefined symbols for architecture x86_64:
  "_pcre_free_study", referenced from:
      _ngx_http_lua_regex_free_study_data in ngx_http_lua_regex.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [objs/nginx] Error 1
make[1]: *** [build] Error 2
make: *** [all] Error 2

そのときは、configureで、

$ ./configure --with-ld-opt="-L /usr/local/lib" --with-luajit

としたら、make出来ました。


たいてい何でもPHPで書く派なのですが(主にtype-hintingが好きなのと、コンパイル言語はコンパイルが長くて面倒だからです。0.1sec以内に結果が返ってきて欲しい派)、サーバーサイドではあまり速度が出なくて悲しい思いをしていました。Nginx+PHP-FPMでHello Worldで3万req/secぐらいが限界感ありました。

かと言ってC言語で書くのも辛いので、いろいろと調べてみたらLuaが良さそうな感じでした。Luaを動かすのはOpenRestyというものが良さそうです。どうやらNginxにいろいろとextensionを施したもののようです。

カジュアルに Nginx で Lua したい — openresty(ngx_openresty) 使う

↑主にこちらに触発されました。mrubyも試してみたいのですがインストールが上手く行かなくて詰まっているのでluaだけやってみました。

OpenRestyの導入とテスト

↑導入はこちらを参考にさせていただきました。ありがたや〜。

G-WANはなぜ速いのか?をnginxと比べながら検証してみた

↑ nginx.confの設定はこちらを参考にさせていただきました。もっと早い設定があるよ!と知っている方はメールやはてブやトラックバックで教えていただけると嬉しいです。しかしながら”worker_cpu_affinity”の書き方がいまいち分からないです(←アホ)

OSはUbuntu 12.04 AMD64です。

OpenRestyのインストール作業ログ

# このマシンのOSのバージョンを表示しておきます
$ uname -a
uname -a
Linux ratta 3.5.0-39-generic #60~precise1-Ubuntu SMP Wed Aug 14 15:38:41 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

$ sudo apt-get install libpcre3 libpcre3-dev libssl-dev
$ cd 
$ wget http://openresty.org/download/ngx_openresty-1.4.2.9.tar.gz
$ tar zxvf ngx_openresty-1.4.2.9.tar.gz
$ cd ngx_openresty-1.4.2.9/
$ ./configure --with-luajit
$ make
$ make install
$ mkdir conf
$ mkdir logs

# confファイルを書きます(後ろの方で、具体的な書式を書いていますので参照してください)
$ vi conf/nginx.conf

# 起動スクリプトを書きます(後ろの方に$ catした具体的なものがありますので参照してください)
$ vi run_openresty.sh
$ vi stop_openresty.sh

# 起動
$ sh run_openresty.sh

# 起動しているのか確認 (↓のようにnginx.confと出てきたらOK)
$ netstat -natp | grep nginx
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      9254/nginx.conf 

# アクセス
$ curl localhost:8080
<p>hello, world! Lua is working!!!</p>

# nginx.confの具体例
$ cat conf/nginx.conf 
error_log logs/error.log;
worker_processes 8;

events {
    worker_connections 1024;
    accept_mutex_delay 100ms;
}
http {
    keepalive_requests 500000;
    tcp_nopush on;
    server {
        listen 8080;
        location / {
            default_type text/html;
            content_by_lua 'ngx.say("<p>hello, world! Lua is working!!!</p>")';
        }
    }
}

# 起動スクリプトの具体的な例
$ cat run_openresty.sh 
# /usr/local/openresty/nginx/sbin/nginx
/usr/local/openresty/nginx/sbin/nginx -p `pwd` -c conf/nginx.conf

$ cat stop_openresty.sh 
# /usr/local/openresty/nginx/sbin/nginx
/usr/local/openresty/nginx/sbin/nginx -s stop -p `pwd` -c conf/nginx.conf

パフォーマンスも見てみました。

トポロジーは、
—–
サーバー(OpenRestyが動いている方) <=> GbEスイッチングハブ <=> クライアント(wr
kベンチで測定)
—–
こんな感じです。スイッチングハブ1個を経由しています。

サーバーとクライアントのスペックを書きます。

* サーバー(富士通サーバー、PRIMERGY MX130S2)
– Optern 3280
– DDR3-1333 4GB * 2
– 1TBHDD (Hitachi HDT721010SLA360)
– NIC (Broadcom BCM57780)
– Linux version 3.5.0-39-generic (buildd@roseapple) (gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) ) #60~precise1-Ubuntu SMP Wed Aug 14 15:38:41 UTC 2013 (Ubuntu 3.5.0-39.60~precise1-generic 3.5.7.17)
– gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)

* クライアント(NECサーバー、
– NEC Express5800/S70 [N8100-9023]/MSS0332
– Intel(R) Celeron(R) CPU G540 @ 2.50GHz stepping 07
– DDR3-1333 8GB*2
– Seagate 500GBHDD (ST500NM0011)
– NIC (Intelのもの、型番不明)
– Linux version 3.2.0-52-generic (buildd@roseapple) (gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) ) #78-Ubuntu SMP Fri Jul 26 16:21:44 UTC 2013 (Ubuntu 3.2.0-52.78-generic 3.2.48)
– gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)

クライアントから、wrkというツールでベンチマークを取ります。3回ぶんほど結果を書いておきます。
$ ./wrk -c 300 -t 4 -d 5 http://192.168.1.2:8080/
Running 5s test @ http://192.168.1.2:8080/
  4 threads and 300 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.37ms    2.49ms 207.46ms   92.09%
    Req/Sec    34.05k     9.19k   68.56k    63.44%
  633839 requests in 5.00s, 129.36MB read
Requests/sec: 126745.97
Transfer/sec:     25.87MB

$ ./wrk -c 300 -t 4 -d 5 http://192.168.1.2:8080/
Running 5s test @ http://192.168.1.2:8080/
  4 threads and 300 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.41ms    1.94ms  29.99ms   78.46%
    Req/Sec    33.14k     8.92k   72.00k    66.76%
  616083 requests in 5.00s, 125.73MB read
Requests/sec: 123212.76
Transfer/sec:     25.15MB

$ ./wrk -c 300 -t 4 -d 5 http://192.168.1.2:8080/
Running 5s test @ http://192.168.1.2:8080/
  4 threads and 300 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.17ms    1.75ms  27.28ms   76.84%
    Req/Sec    36.41k     9.60k   60.78k    68.45%
  661611 requests in 5.00s, 135.03MB read
Requests/sec: 132320.69
Transfer/sec:     27.00MB

10万req/secを軽々超えているとはすごいですね。このときのサーバーのdstatを見てみます。

# dstat
You did not select any stats, using -cdngy by default.
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw 
  0   0  99   0   0   0|  64k  117k|   0     0 |   0     0 | 272   211 
  0   0 100   0   0   0|   0     0 | 328B 1122B|   0     0 |  90   198 
  0   0 100   0   0   0|   0     0 |  70B  358B|   0     0 |  94   209 
 13  15  70   0   0   2|   0     0 |4770k   12M|   0     0 |  14k 1131 
 28  34  34   0   0   4|   0     0 |  14M   34M|   0     0 |  23k 5076 
 31  38  25   0   0   6|   0     0 |  15M   38M|   0     0 |  33k   10k
 27  39  26   0   0   7|   0     0 |  13M   33M|   0     0 |  28k 8995 
 25  42  27   0   0   6|   0     0 |  14M   35M|   0     0 |  29k 9323 
  9  12  76   1   0   2|   0    16k|8695k   22M|   0     0 |  11k 3715 
  0   0 100   0   0   0|   0     0 |  70B  358B|   0     0 |  91   183

↑まだCPUに余裕はありそうですね。ネットワークも最大38MB/sということなので、Gbに達していません。もう少し、nginx.confのworker processを増やせば良くなりそうな気はします(↑ではworker processは8。CPUのコア数と同じにしてます)。
-> worker process 16にしてやってみたら(↓こういう感じです)、若干遅くなってしまいました。
# 〜中略〜

worker_processes 16;
worker_cpu_affinity 0000000000000001 0000000000000010 0000000000000100 0000000000001000 0000000000010000 0000000000100000 0000000001000000 0000000010000000 0000000100000000 0000001000000000 0000010000000000 0000100000000000 0001000000000000 0010000000000000 0100000000000000 1000000000000000;

# 〜中略〜

ついでにサーバーとクライアントを逆にしてやってみました。

$ ./wrk -c 300 -t 4 -d 5 http://192.168.1.3:8080/
Running 5s test @ http://192.168.1.3:8080/
  4 threads and 300 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.51ms    5.87ms  41.93ms   85.03%
    Req/Sec    22.16k     2.79k   33.11k    74.87%
  425495 requests in 5.00s, 86.84MB read
Requests/sec:  85097.54
Transfer/sec:     17.37MB

$ ./wrk -c 300 -t 4 -d 5 http://192.168.1.3:8080/
Running 5s test @ http://192.168.1.3:8080/
  4 threads and 300 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.63ms    6.14ms  56.30ms   86.82%
    Req/Sec    21.61k     2.63k   30.47k    71.60%
  417111 requests in 5.00s, 85.13MB read
Requests/sec:  83419.06
Transfer/sec:     17.02MB

$ ./wrk -c 300 -t 4 -d 5 http://192.168.1.3:8080/
Running 5s test @ http://192.168.1.3:8080/
  4 threads and 300 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    48.28ms  140.55ms 491.70ms   90.83%
    Req/Sec    19.89k     6.81k   28.55k    89.08%
  386907 requests in 5.00s, 78.96MB read
Requests/sec:  77375.75
Transfer/sec:     15.79MB

Celeron G540でも8万req/sec程度出ています。なかなかですね!そのときのdstatを見てみます↓。

$ dstat
You did not select any stats, using -cdngy by default.
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw 
  2   2  94   0   0   1|  26k   47k|   0     0 |   0     0 |2081  2469 
 10   8  81   0   0   3|   0     0 |1870k 4529k|   0     0 |4276   705 
 50  35   0   0   0  14|   0     0 |9280k   23M|   0     0 |  20k 1302 
 49  35   2   0   0  15|   0     0 |9032k   22M|   0     0 |  21k 3742 
 53  35   1   0   0  11|   0     0 |9189k   23M|   0     0 |  21k 3647 
 49  36   1   0   0  14|   0     0 |9188k   23M|   0     0 |  21k 3674 
 42  27  20   1   0  11|   0    16k|7195k   18M|   0     0 |  17k 3183 
  0   0 100   0   0   0|   0     0 |  70B  358B|   0     0 |  57   132

↑CPUを使いきっていますね。これ以上性能の向上の余地はなさそうです。Opteronの方はまだCPUに余裕がありましたが、使い切ることが出来ませんでした^^;どうしたら使い切れるのか。。。

追記:

Luaで動かしたときと、静的なファイルのパフォーマンスを比較してみました。Opteron3280の富士通サーバーの方をサーバーに、G540のNECサーバーの方をクライアントにして比較です。

# Lua
$ ./wrk -c 300 -t 4 -d 5 http://192.168.1.2:8080/lua
Running 5s test @ http://192.168.1.2:8080/lua
  4 threads and 300 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.04ms    2.17ms 207.70ms   90.28%
    Req/Sec    36.86k     8.72k   68.33k    61.65%
  678634 requests in 5.00s, 138.50MB read
Requests/sec: 135696.78
Transfer/sec:     27.69MB

# 静的ファイル1回目
$ ./wrk -c 300 -t 4 -d 5 http://192.168.1.2:8080/helloworld.html
Running 5s test @ http://192.168.1.2:8080/helloworld.html
  4 threads and 300 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.51ms    2.45ms  41.96ms   89.89%
    Req/Sec    30.60k     9.85k   83.44k    70.29%
  570199 requests in 5.00s, 140.30MB read
Requests/sec: 114045.41
Transfer/sec:     28.06MB

# 静的ファイル2回目
$ ./wrk -c 300 -t 4 -d 5 http://192.168.1.2:8080/helloworld.html
Running 5s test @ http://192.168.1.2:8080/helloworld.html
  4 threads and 300 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.08ms    1.36ms  35.06ms   67.98%
    Req/Sec    37.19k     8.88k   59.44k    68.30%
  685150 requests in 5.00s, 168.58MB read
Requests/sec: 137006.35
Transfer/sec:     33.71MB

このように、Luaでも静的ファイルと変わらない速度が出ています。すごいですね。

ついでにhhvmも試してみました。hhvmの導入方法は、公式サイトからUbuntuのレポジトリがあったのでそれをそのまま使いました。

# バージョン確認
$ hhvm --version
HipHop VM v2.1.0-dev (rel)
Compiler: tags/HPHP-2.1.0-0-gee8da60efaad9398eb4fb5909ede5cf6bb662128
Repo schema: 56ccb9c5c4a45b351e042a3b4da0b98d7e4e27f9

# hello worldを出力するPHPスクリプトを作成
$ echo '<?php echo "Hello, World by hhvm.";' > helloworld.hhvm.php

# 動くか確かめる
$ php helloworld.hhvm.php 
Hello, World by hhvm.

# サーバー起動
$ hhvm -m server -p 8888

結果
# 動いているか確かめる
$ curl http://192.168.1.2:8888/helloworld.hhvm.php
Hello, World by hhvm.

# wrkでベンチマーク
$ ./wrk -c 300 -t 4 -d 5 http://192.168.1.2:8888/helloworld.hhvm.php
Running 5s test @ http://192.168.1.2:8888/helloworld.hhvm.php
  4 threads and 300 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     9.97ms    5.20ms 330.31ms   97.92%
    Req/Sec     7.51k     1.51k   12.50k    87.56%
  142676 requests in 5.00s, 21.36MB read
Requests/sec:  28534.14
Transfer/sec:      4.27MB

まあこんなもんですよね。PHPでNginx+PHP-FPMでAPCなどを使った場合でもこんな感じでしたので。。。