MessagePack for Lua を公開しました

先週から少しずつ書いていた MessagePack for Lua が動くようになったので一旦公開しようと思います。

MessagePack とは

MessagePack とは 古橋くん(id:viver, @frsyuki)が開発した、高速なバイナリシリアライズ形式です。「速くてコンパクトなJSON」と言われる通り、JSONとほぼ同等のことを行えます。本当に速くてコンパクトなので、高頻度でJSON形式のデータをやりとりしているようなアプリケーションでは、 MessagePack に切り替えるだけでもかなりの効率改善につながるかと思います。

弊社の製品でも、RPC的な部分でものすごくお世話になっております(^q^) あ、関係ないですが10日夜8:00から社内セミナーをustで公開するので是非見てくださいね。 Twitter@preferred_jp をフォローすると情報が流れてくると思います。論文紹介やアニメやサッカーの話題など色々流れてくるので是非フォローしちゃってください。

動作環境とインストールに必要なもの

今のところ Ubuntu9.10 でしか動作確認をしていません。明日 Mac でも動作確認をしてみます。 Windows 対応は未定です(cygwin ならコンパイルできるかも)。

MessagePack for Lua を使うためには、 MessagePack と liblua5.1 が必要です。 liblua に関しては、 apt や yum などでインストールすることをお勧めします。

インストール

http://cloud.github.com/downloads/nobu-k/mplua/mplua-0.1.0.tar.gz から MessagePack for Lua をダウンロードしてください。

あとは下記のコマンドを打つことでインストールが完了します。

$ tar zxvf mplua-0.1.0.tar.gz
$ cd mplua-0.1.0
$ ./configure
$ make
$ sudo make install

ソースコード

$ git clone http://github.com/nobu-k/mplua.git

で落とせます。

使い方

インターフェイスPython binding がものすごく格好良かったので、真似させてもらいました。

以下がサンプルコードです。

シリアライズ
require "msgpack"

p = msgpack.Packer()
s1 = p:pack({1, 2, 3})
s2 = p:pack({4, 5, 6})

print(string.byte(s1, 1, #s1)) -- 147 1 2 3
print(string.byte(s2, 1, #s2)) -- 147 4 5 6

packは可変長引数を受け取るので、次のように書くことも可能です。

data = p:pack(1, 2, 3, "strings", {"a", "r", "r", "a", "y", "s"},
              {t = "a", b = "l", e = "s"; 1, 2, 3, 4})
シリアライズ
require "msgpack"

buf = string.rep('\147\1\2\3', 5)
u = msgpack.Unpacker()

u:feed(string.sub(buf, 1, 9))
for o in u do
  print(unpack(o))
end
print('--------')
u:feed(string.sub(buf, 10))
for o in u do
  print(unpack(o))
end

-- 出力
1	2	3
1	2	3
--------
1	2	3
1	2	3
1	2	3

ちなみに、feedも可変長引数を受けとることができます。

現状でシリアライズ/デシリアライズできるのは

  • 数値
  • bool
  • 文字列
  • 配列
  • テーブル

です。 LuaPHP と同じく、配列とテーブル(ハッシュ) を区別しません(内部的にはしていますが)。しかし、 MessagePack for Lua では、配列とテーブルを区別してシリアライズすることが可能です。配列の定義は、「0より大きい整数値キーのみで構成され、キーの最大値がテーブルの要素数と等しいテーブル」です。それ以外はすべてテーブルとして扱われます。

また、数値は、一応「整数」と「実数」を区別してシリアライズしています。しかし、 Lua では数値をすべて double (64bit 浮動小数点数) で持つため、 整数の値は 52bit 程度しか正確に表現することができません。そのため、他言語とやりとりを行った場合に少し問題が起こる可能性があります。

今後の予定

今はまだしょぼいことしかできませんが、ちょっとずつ拡張して行こうと思っています。

コールバック対応

例えば、シリアライザでは

function packer (s)
  write(s)
end

p = msgpack.Packer(packer)

とすると、 p:pack が文字列を返すのではなく、 packer 関数が呼ばれるようになったりなど。デシリアライザでは

function feeder ()
  -- どっかからデータを読み込み
  return 読み込んだデータ
end

u = msgpack.Unpacker(feeder)
for o in u do
  -- feeder から入力を読み込んでデシリアライズ
end

みたいなのにも対応しようと思っています。関数の仕様は変わると思いますが。

ネイティブ packer, unpacker の対応

Lua の魅力の一つとして C/C++ とデータのやりとりがものすごく自然にできる点があげられます。現バージョンでは Lua 内で完結していますが、ネイティブな packer, unpacker も Lua から使えるようにする予定です。そうすることで、 Lua で作成した packer を C から使ったり、その逆を行ったりすることができるようになります。

カスタムシリアライザ/デシリアライザ

metamethod __serialize, __deserialize (もしくは __pack, __unpack) を定義したテーブル、ユーザデータに関しては、その関数に Packer, Unpacker を渡して柔軟なシリアライズを可能にしようかと考えています。具体的にどんな感じにするかは悩み中。

と言うか、現状だとテーブルに関数が含まれてるとシリアライズに失敗しちゃうので、その辺の対応もしないといけないんですよね。

高速化

後は高速化ですね。せっかく id:viver が頑張って速いのを書いたのに、ライブラリの層で減速させてしまっては申し訳ないです。

おわりに

そんなわけで、出来上がったばかりの MessagePack for Lua の使い方と今後の予定を紹介しました。現在 MessagePack 普及運動を行っておりますので、是非、 Lua binding だけでなく、他のも使っていただけるとうれしいです。 MessagePack RPC も開発されているので、頑張って国産品で thrift に勝ちたいですね。

漏れてたらごめんなさい!!

あと、MessagePack for Lua の技術メモ的な内容も後日書こうと思っています〜。効率的な配列判定方法や現在抱えている問題などを書く予定。

最後になりますが、僕は Lua 初心者なので、もし Lua を使い込んでいる方がソースを見る機会がありましたら、しょぼいところをコメントして頂けるとうれしいです。また、こういうちゃんとした形でソフトウェアを公開するのも初めてなので、オープンソース的な作法がなってないところもあるかと思います。その辺も指導していただけると感激です。よろしくお願いします〜。

Twitter Streaming API を gardenhose レベルで利用する

id:awakia に教えて貰いました。

Twitter Streaming API の sample を使うと、正確に確認したわけではありませんが、全発言の1/20を取得できます。やってみると分かりますが、この1/20というのは結構シビアで、全然自分の発言が拾われません。秘宝伝の高確での解除率が1/16.8だから、それより悪かったら引ける気がしませんよね。何でもないですすいませんでした。

余談ですが、全発言の1/20というのは、具体的には発言IDの下二桁が特定の数字である発言のみが拾えるということです。自分のところだと確認した限りでは04〜08でしたが、もしかしたら環境によって変わるかも知れません。まあ、100個中5個しか拾えないということですね。

そんなわけで、通常の sample はもの凄くショボイのですが、そのしょぼさをちょっとだけ改善できるのが gardenhose です。正確に計測していませんが、通常の sample の4倍くらい拾えているようです。拾われる確率が 1/5 だったらまだ頑張る気になりますね。

gardenhose を使うためには申請が必要になります。gardenhose の申請をするには、 Twitter にログインした状態で http://twitter.com/help/request_streaming にアクセスして必要事項を埋めて Submit するだけです。ちょっとすると gardenhose を使えるようになります。正確には sample を gardenhose レベルで使用可能になります。なので、 API のクライアントを変更する必要はありません。

ちなみにもう一個上のレベルの firehose を使うと全発言が取れるらしいですが、よっぽどのことがあっても使わせて貰えないそうなので諦めましょう。あと、もしかしたら将来的に申請方法が変わってしまうかも知れないので、そうなったら他のページをググってください。

C++で作ったwxWindowに、Luaで作ったコントロールを設置する(wxWidgets 2.8.10を使用)

wxLua を使うと Lua から wxWidgets を簡単に使うことができる。もちろん、フレームから何から全部 Lua で書けてしまうので、そうしても構わないのだけど、コアはやっぱり C++ で書きたい! GUI も大枠は C++ で書いて、局所的に Lua で拡張したい!! そして、それができれば、 LuaGUI を拡張するプラグインなんかも簡単にサポートできちゃうし、夢が広がりすぎて困る。

wxLua はそんな欲求も満たしてくれる。

試しにやってみること

C++ で wxFrame を作成し、その上に Lua で wxButton を2個作って設置(ついでに wxBoxSizer も使ってみる)。片方のボタンは Lua 側でイベントを処理する。もう片方のボタンがクリックされたら C++ 側の wxFrame でイベントを処理するようにする。

サンプルコード

wxLua の中のサンプルがでかすぎたので、自分で小さめの奴を書いた。コンパイル&実行には LuawxLuawxWidgets と boost が必要。

また、コンパイル時に -I$(include-dir)/wxbind/setup 的なオプションを与えておく必要がある。

#include <boost/shared_ptr.hpp>
#include <wx/wx.h>
#include <wxlua/include/wxlstate.h>
#include <wxbind/include/wxbinddefs.h>
#include <wxbind/include/wxcore_bind.h>
WXLUA_DECLARE_BIND_WXCORE

class Frame : public wxFrame {
  DECLARE_EVENT_TABLE()
public:
  Frame() : wxFrame(NULL, -1, wxT("test"), wxDefaultPosition, wxDefaultSize) {}
  void OnHogePushed(wxCommandEvent& event);
};

BEGIN_EVENT_TABLE(Frame, wxFrame)
  EVT_BUTTON(10, Frame::OnHogePushed)
END_EVENT_TABLE()

void Frame::OnHogePushed(wxCommandEvent& event) {
  wxMessageBox(wxT("hoge!!"));
}

class App : public wxApp {
public:
  virtual bool OnInit();

private:
  boost::shared_ptr<wxLuaState> state;
};

IMPLEMENT_APP(App);

bool App::OnInit() {
  WXLUA_IMPLEMENT_BIND_WXCORE

  Frame* frame = new Frame();

  state.reset(new wxLuaState(true));
  if (state->LuaDoFile(wxT("test.lua")) != 0) return false;
  state->lua_GetGlobal("init");
  state->wxluaT_PushUserDataType(frame, wxluatype_wxFrame, true);
  if (state->LuaPCall(1, 0) != 0) return false;

  frame->Show(true);
  SetTopWindow(frame);
  return true;
}

test.lua

require "wx"

function init(window)
  sizer = wx.wxBoxSizer(wx.wxVERTICAL)

  pushMe = wx.wxButton(window, -1, "Push me!!")
  pushMe:Connect(wx.wxEVT_COMMAND_BUTTON_CLICKED,
                 function (event) wx.wxMessageBox("ktkr") end)
  sizer:Add(pushMe)
  sizer:Add(wx.wxButton(window, 10, "Hoge"))
  window:SetSizer(sizer)
end

cpp の方の説明

wxWidgetsLua と C の連携方法の説明は省略する。

まず色々作業を始める前に、バインディングを初期化しておく必要がある。これは簡単で、まずソースファイルのどこかに

#include <wxbind/include/wxbinddefs.h>
WXLUA_DECLARE_BIND_***

と書いておく。***の部分には WXCORE, WXBASE, WXXRC など、使いたい wxWidgets の機能を入れる。 wxbinddefs の中に全部かいてあるので見ながら使いたいものを書けばいいし、困ったら wxLUA_DECLARE_BIND_ALL としておけばいい。

次に、ソースのどこか(初期化処理をしているところが望ましそう)に

WXLUA_IMPLEMENT_BIND_***

と書く。今回は App::OnInit に書いた。 *** の部分はさっき書いた WXLUA_DECLARE_BIND_*** と同じものを書く。

初期化できたら Lua の関数を呼んでみる。

  state.reset(new wxLuaState(true));
  if (state->LuaDoFile(wxT("test.lua")) != 0) return false;
  state->lua_GetGlobal("init");
  state->wxluaT_PushUserDataType(frame, wxluatype_wxFrame, true);
  if (state->LuaPCall(1, 0) != 0) return false;

ここでは、

  1. lua_State を初期化(wxLuaStateを利用)
  2. test.lua をロード
  3. init関数を呼ぶ

という感じの処理をしている。

Lua の関数を呼ぶときに問題となったのは、C++ の wxObject* をどうやって Lua に渡すか、という部分。 もちろん userdata として渡すんだけど、ちゃんと使うためには metatable の設定をしなければならない。各 wxWidgets のクラス用の metatable は一応 wxLua のレジストリ(Windows のではなく Lua の中のレジストリと呼ばれるテーブル)に入っているらしい。でも、 push する度にわざわざユーザデータを構築するのは相当面倒くさい。

そこで、 wxLuaState::wxluaT_PushUserDataType を利用する。

state->wxluaT_PushUserDataType(frame, wxluatype_wxFrame, true);

1個目の引数が wxObject* 、2個目が型情報。型情報には wxluatype_*** という、目的の型のものを渡せば良い。今回は wxFrame を渡したかったので、 wxluatype_wxFrame とした。良くわからなかったら wxLua に入っているサンプルの wxluacan では wxDC を渡しているので、その辺も見てみると良いかもしれない。 wxluatype_*** は wxbind/include 以下のどこかに書かれているので適当に grep すると良い。

3個目の引数(track)はよくわからないんだけど、wxlstate.hには

// If track=true then push the obj_ptr as a lightuser data key into the
// wxlua_lreg_weakobjects_key table of the Lua LUA_REGISTRYINDEX table so
// that if we need to push it again we just push the already created full userdata value.

という説明がある。本当に良く分からない。とりあえずコードを読んでみた感じだと、 Luagc に関係するものらしいので、もう少し勉強しないと正確なことは書けなさそう。サーセン

まあ後は wxLua とは直接関係ないんだけど、 id=10 のボタンがクリックされたら Frame::OnHogePushed が呼ばれるイベントを定義してる。クリックされるとメッセージボックスで "Hoge!!" というのが出てくる。

Lua コードの説明

init に渡された window にコントロールを追加している。 init では wxBoxSizer を作って、そこにボタンを追加している。

1個目のボタンは、 Lua 側でイベント処理をしており、クリックされたら ktkr というメッセージを出す。2個目のボタンは id=10 としておいて、クリックしたら C++ 側で定義されているイベントが呼ばれるといいなーという感じ。

実行してみる

めんどいのでスクリーンショットなどは貼らないけど、ちゃんと動いた!

終わりに

wxLua を C++ から使うための情報がかなり少なかったので、色々調べて書いてみた。 wxLua は使えるとかなり便利なライブラリではあるものの、関数に変なプリフィックスがついていたり、pkg-config を使えないので configure.ac を書くときにいろいろと大変だったりと、面倒くさい部分も多い。Lua から使う分には、かなり直感的に書けるようになっているので素晴らしいんだけどね・・・。

まあそんなわけで、wxWidgets はいろんな言語のバインディングがあるけど、ここまで簡単に C++ とやりとりできるのは多分 wxLua だけなのでおすすめ!(と書くと他の人が「それ、○○でもできるよ」という記事を書いてくれるメソッド)

Amazon EC2のアカウントを作成してみた

クラウドAMAZON EC2/S3のすべて (ITpro BOOKs)

クラウドAMAZON EC2/S3のすべて (ITpro BOOKs)

この本を参考にアカウントを作成してみた。まだアカウントが有効になってないので、その間にメモを書いてみる。

アカウントの作成は上記の本を参考にすることですごくスムーズに行うことができた。この本は全体的に説明がすごく詳しくてわかりやすいので、かなりおすすめ。アメリカ語が読めなくても全然問題なく作業できる。ただ一点だけ本に書いていない項目があった(最近追加されたのかな?)。159ページ「6.2.1 Amazon EC2の利用前準備」の(2)に、Sign Up For Amazon EC2 をクリックという説明がある。本では、この後すぐ(3)に移っているけど、今は(2)と(3)の間にもう1個ステップが追加されている。

現在は、Sing Up For Amazon EC2というのをクリックすると、電話を使った verification が行われる模様。電話と言っても決して Amazon EC2 を使う敷居が高くなったわけではなく、至って簡単な作業だった。会話は一切ない。必要なステップは以下のとおり。

  1. 電話番号を入力して "Call me now" をクリック
  2. 電話がかかってきたらブラウザに表示されている PIN number をプッシュ
  3. 電話が終わったら表示されてるボタンをクリック

1. 電話番号入力

国コードと電話番号を入力するためのボックスが用意されているので適切に入力する。国は Japan を選ぶ。電話番号は、最初の0を取る必要も81をつける必要もなくて、普通に普段使っている番号を入力すればおっけー(携帯だったら090-xxxx-yyyyという感じ)。

入力したら "Call me now" をクリック。

2. 電話がかかってくる!

マジでかかってくる。電話に出ると2,3秒の沈黙の後におっさんがなんかしゃべり始めるので、とりあえずしゃべり終わるまで待つ(待たなくてもいいかもしれない)。しゃべり終わったら、ブラウザに表示されている4桁の PIN number を入力。そうすると大丈夫だったよ!的なことを言われ、ブラウザ上のページも勝手に次のステップへ移行。電話と連動してページが動いてちょっと格好良かった。

ちなみに、PIN number は一桁ずつ喋っても登録できるらしい。

3. 完了

電話認証後に表示されるボタンをクリックすると本の(3)と同じものが出てくる。そこから先は本にかかれている通り。

楽ちん!で、日記を書き終わってもアカウントが有効にならないんだけど、大丈夫なのかな。

runtime.GOMAXPROCS で CPU をフル活用

CPU使用率が100%を越えなくて悩んでたら id:kzk が発見してくれました!やたー!

import "runtime"

func main() {
  runtime.GOMAXPROCS(コア数);
}

という感じにすると本気を出してくれます!が、下のソートはコア数1のときが一番速かった・・・。まあチャンネル使ってやるもんじゃないから仕方ないけれど・・・。

あと下のソートは、ちゃんとO(NlogN)っぽい実行時間で動いてました。すごい〜。でもメモリいっぱい食うよ。

Go で無駄にチャンネルを使ってクイックソートのようなものを書いてみた

-1がEOF的な用途で使われている感じ。Core i7でCPU100%までしか食わないんだけど、書き方が悪いのかそういうものなのか。チュートリアルのsieveで興奮して適当に書いただけなので、明日もう少しドキュメント読んでみよー。

package main
 
import "fmt"
import "rand"
import "time"
 
type Callback func (int);
 
func foreach(a chan int, f Callback) {
  for {
    v := <-a;
    if v < 0 { return }
    else { f(v) }
  }
}
 
func partition(x int, a chan int) (chan int, chan int) {
  l := make(chan int);
  h := make(chan int);
 
  go func() {
    foreach(a,
      func (v int) {
        if x >= v { l <- v }
        else { h <- v }
      });
    l <- -1;
    h <- -1
  }();
  return l, h
}
 
func qsort(a chan int) chan int {
  out := make(chan int);
  go func() {
    x := <-a;
    if x < 0 {
      out <- -1;
      return
    }
 
    l, h := partition(x, a);
    l = qsort(l);
    h = qsort(h);
 
    foreach(l, func (v int) { out <- v });
    out <- x;
    foreach(h, func (v int) { out <- v });
    out <- -1
  }();
  return out
}
 
func main() {
  rand.Seed(time.Seconds());
 
  a := make(chan int);
  go func() {
    for i := 0; i < 100000; i++ {
      a <- rand.Int();
    }
    a <- -1;
  }();
 
  res := qsort(a);
  foreach (res, func (v int) { fmt.Println(v) });
}

Go での簡単な Makefile の書き方

include $(GOROOT)/src/Make.$(GOARCH)

TARG=コマンド名
GOFILES=\
        file1.go\
        file2.go\
        ...

include $(GOROOT)/src/Make.cmd

こんな感じのを用意するだけでいいらしい。最後の Make.cmd を Make.pkg にすると .a を作ってくれる。らくちん!