Hatena::ブログ(Diary)

kyabの日記

2014-02-21 mrubyでのありがちなミス4つ

よくやってしまう+これからもやってしまうこと。

(2014/4/28追記:1つめにmruby-clang-plugin紹介)

(2014/4/6追記:4つめ)

mrb_funcall()の可変引数部分は全部mrb_valueじゃないといけない。

ついついintなどをそのまま突っ込んじゃう。可変引数なので何を入れてもコンパイルできちゃう。けど動かない。以下使ってmrb_valueにしてから使う。

  • mrb_fixnum_value()
  • mrb_float_value()
  • mrb_str_new_cstr()/mrb_str_new_lit()

(@さんのmruby-clang-pluginを使えばclang使ってコンパイル時にチェックできるぽいです。自分はまだ試せてない。。)

RSTRING_PTR()はNULL終端とは限らない。

mrb_valueの中身が文字列の場合にRSTRING_PTR()使ってchar *取り出せるけどNULL終端とは限らない。printf()に渡して死ぬ。NULL終端が欲しい場合は

  • RSTRING_LEN()で長さを把握して使う
  • mrb_str_to_cstr()でNULL終端文字列を新たに作る。内部で新たにオブジェクトが作られるのでメモリ気にするときは注意。

そもそも普通のRubyプログラムとして間違ってる。

思った通り動かない!なんで?なんで?→単にRubyとして間違ってることがまれによくある。落ち着いてCRubyで検証する。


マクロ定義(MRB_XXX)のミスマッチ

build_config.rbでconf.cc.definesにMRB_WORD_BOXINGとか追加したのに、mrubyを使うプログラム側をコンパイルするときに-DMRB_WORD_BOXING追加するのを忘れてmrb_open()が落ちる/止まる。

使えるMRB_XXXXはinclude/mrbconf.hにあって、全部が全部使う側のプログラムでも指定しないと問題になるわけではないけど、MRB_WORD_BOXING, MRB_USE_FLOAT,MRB_NAN_BOXING,MRB_ENDIAN_BIGあたりは根幹となるmrb_value構造体のレイアウトが変わったりするので特に注意。それ以外も今後変わる可能性があるので基本全部合わせるべき。

自分の場合はメモリ節約しようとして試行錯誤しているうちにbuild_config.rbと使う側のMakefileを合わせるのを忘れて「mrb_open()が返ってこない。メモリ足らない?いやまだ足りてるはず」とかでハマる。

この辺は色々議論もされてるみたいなので改善されていきそう。でもbuild_config.rbはRubyで書けるし、自動生成してもいいし、自分でも工夫できる。

2014-02-15

CRubyのGVLとビジーループ

02:34

mrubyのVMのマルチスレッド対応がgithubにて議論されています。

multi-thread support on the RiteVM #1657

自分もthread-safeなVMが欲しいなぁと思っています。STM32F4DiscoveryにFreeRTOS載せて使ってみたい。

並行・並列処理の戦国時代?

さて、マルチコアが普通になったし、WebアプリのC10K問題があるので並行・並列処理は昨今のトピックです(多分)。

ただマルチスレッドプログラミングは難しすぎ!というは昔から言われていたことです。

で、もうちょい並行・並列処理を書きやすく出来ないのかよ?ということでErlang,go,Scala(Actor),EventMachine,Thread pool ,node.js,deferred,future,java.util.concurrent色々出てきました (言語とライブラリ、書き方のスタイルごちゃまぜですが)。全部見てたら頭おかしくなりそう・・・。

勝手にまとめると

まず、

A「どうせボトルネックはファイル読み書きとかネットワーク、つまりI/Oじゃん。その辺は全部ノンブロッキング/非同期API使おうぜ!イベントドリブン最強!」

っていうのがありますね。node.jsなんかはシングルスレッドですが、パフォーマンスも良いみたいだし一定の支持を得てる。ただコールバック地獄になったりするんで、いい感じに書けるようにDeferredとかasyncみたいなスタイルも併用。EventMachineもチラッとしか見てないけど似たような感じ? 排他を気にしなくて良いのは嬉しい。

B「俺のマシンはマルチコアなのになんで一つだけ100%で他のやつは暇してるんだ。納得出来ない! 派」

無意味にスレッド増やしてもコア数はそんな多くないので重い計算はいい感じに書くと勝手に並列化してくれると嬉しい。C#のParallelとかJavajava.util.Concurrentとか?並列コンテナってやつ。スレッドプールで使いまわしてコストも低め。

C「forkすればいいじゃん。派」

一番簡単にマルチコア使えますね。parallel gemなんか使うとforkした子プロセスから計算結果返すのも余裕。cygwinみたいに死ぬ気でやればWindowsもOK?

結局どうなのよ?

実際にはAとB(とC)は常に完全に別れた要求でもなくて、要するに

「わけわかんねぇけど俺はいい感じに書きたいだけ!I/Oは使えるならノンブロッキング使うとか、重い計算はマルチコア使いまくりでよきにはからってよ!あ、コンテキストスイッチに時間使うとか勘弁ね。あと俺は普通のマルチスレッドとMutexで排他するスタイルではもう書かないって決めてるから。後もう一回言うけどいい感じに書きたいから」

ってことですかね。

アクターモデルってのがあります。Erlangで有名になって、Scalaも売り(の一つ)にしてるし、goではgoroutine、あとRubiniusもActor持ってます。

「Don't communicate by sharing memory; share memory by communicating. 」とか優等生っぽい。ただ馴染めるのかまだよくわかりません。ある程度使ってみないとなぁ。goの本積みっぱなし。

そうそう、並行と並列って言葉ですが、こんな感じ?

並行(concurrent):処理を別々に分けて書くこと(同時に実行されるとは限らない)。ふつうのマルチスレッドで書く場合

並列(parallel) : 複数の計算が同時に実行されること。マルチコアが同時に動いてるイメージ

忘れたら"並行 並列 違い"でググればOK.

CRubyのGVL(GIL)

CRubyのThreadクラスは1.8.xまではグリーンスレッドで実装されていました。で、1.9.0ではインタプリタからVMになって、ThreadクラスもNative Thread(OSが提供してるスレッド)で実装されるようになったというのは有名な話。

ただNative Threadになったけど、マルチコアを(ほとんど)活かせない。なぜならVMが動く際、基本的に一つの巨大な排他をしてるから。この排他というかロックをGVL(Giant VM Lock)またはGIL(Giant Interpreter Lock)と呼ぶ。Native ThreadだからVMが検知できないタイミングでスレッドは切り替わろうとするけど、ロックされてるからまた元のスレッドに戻ってくる(正確にはロックを待っている方のスレッドOSのスケジューラのキューに入らない)。

GVLはブロッキングするようなC関数(典型的には各OSのSleep()。write()とかも?)呼び出し中には解放される。というかCで書かれたライブラリ中でそういう風に作ってる。なのでその際他のThreadクラスのインスタンスも動作できる。この時だけはマルチコアが同時に動く(可能性がある)。

C拡張は基本的にGVLがかかった状態で呼び出される。これをしないと、C拡張を常に注意深くスレッドセーフにする必要があるし、そういうことをすべてのC拡張開発者に求めるのはキツかろう、という判断みたい。

ただ、C拡張の中でブロッキング関数を呼び出したり、純粋な重い処理をやるときにGVLを解放してやることはできる。その為にrb_thread_blocking_region()というのが用意されている。よってC拡張の中でrb_thread_blocking_region()が使われている場合はマルチコアが同時に動く(可能性がある)。

GVLはCRubyの実装を単純に保ち、かつシングルスレッド性能を落とさないためには今の所まぁしょうがないよね〜。forkでも使えば〜。っていうところらしい。あとは複数のVMを使う(MVM:multi VM)というのも今後CRubyではあり得る感じ。今はCRubyでサクッとMVMするのは難しいけど、mrubyは簡単にできる。mruby-threadというmrbgemはスレッドごとにVMを分けてる。

で、それで満足できない場合は、JRuby、Rubinius、RubyMotionあたりに逃げちゃう手がある。これらにはGVLがない。内部的には所々排他処理してるけど、GVLよりもっと細かく、複数のMutexをつかってやるらしい。これをfine grained lockなどと呼んでGVLと区別してる。

JRubyJavaだから置いておくとして、RubiniusはC拡張の安全性はどうすんの?って思ったら、前までC拡張にはGVLでロックをかけていたけど、最近はデフォルトでそれもdisableにしたらしい。それでも「みんなフツーに使えてるよ」とのことらしいけど、まぁ楽観的な考えなのかな。CRubyとはユーザベースも違うとは思うけど。

Thread内でのビジーループ

で、そこまで調べて気になったのが、じゃぁ例えば2つのスレッドが(内部でGVLを開放するようなメソッドを一切呼ばないで)ひたすらループしてたらどうなんの?ってこと。片方がGVLをロックしっぱなしだと、もう一方のスレッドは動作するチャンスがなくなっちゃうのでは?

結論からいうと、そんなケースでも時々スレッドは切り替わる。以下の凄まじく長いGVL(=GIL)に関する記事によると、タイマースレッドというのが裏で動いていて、時々フラグを立てるらしい。タイマースレッドはCレベルで書かれていて、もちろんGVLと関係なく動く。そんなこともしてたのか・・。

Nobody understands the GIL - Part 2: Implementation

本当なのか以下のコードで実験してみた。2つのスレッドを作って、aという変数を書き換えるだけ。各スレッドはaの値が書き換わっていたら、その回数を記録する。

a = 1
 
thread_a_preempted_count = 0
thread_b_preempted_count = 0

t1 = Thread.new do 
  10000000.times do|i|
    if (a != 1)
      thread_a_preempted_count += 1
    end
    a = 1
  end
end

t2 = Thread.new do
  10000000.times do |i|
    if (a != 2)
      thread_b_preempted_count +=1
    end
    a = 2
  end
end
 
t1.join
t2.join
 
p thread_a_preempted_count
p thread_b_preempted_count

Windowsでの実行結果:

C:\home\work\ruby_concurrent>ruby --version
ruby 2.0.0p353 (2013-11-22) [i386-mingw32]

C:\home\work\ruby_concurrent>ruby threadatomic.rb
30
34

Mac OSXでの実行結果

[koji@macbookpro:~/work/ruby_concurrent]$ ruby --version
ruby 2.0.0p247 (2013-06-27 revision 41674) [universal.x86_64-darwin13]
[koji@macbookpro:~/work/ruby_concurrent]$ ruby thread_preempt_busy_loop.rb 
1
6

ということで確かにビジーループでも時々スレッドは切り替わる。Native Threadがプリエンプションでちゃんと切り替わるのと同じ感覚で使える。

あと、確かにタイマースレッドが存在することも確認できた。Rubyコード上ではスレッドは3つだけど、プロセスレベルでは4つスレッドがある。

f:id:kyab:20140216022140p:image

2014-01-31 秋葉原のmrubyイベントでデモさせてもらった。

秋葉原で開催された東京Rubyプレゼンテーション2014にパネラーとして参加させていただきました。@さんの紹介に多謝。

自己紹介でホストベースのmirb について簡単にデモしましたのでスライドおいて置きます。

http://www.slideshare.net/kojiyoshioka7/ruby2014-mruby-30670841

はてなに貼る方法がよくわからん。

mruby安定版が発表されたり、あと特にmruby関係者の面々と直接話せてとても有意義でした。

matzに気になっていたマルチスレッド/マルチタスク対応について聞いたところ、POSIX/Windows系以外の組込系のタスクが色々(あんま知らない)なのもあってもう少し検討される感じ。

2013-12-19 [mruby]カスタムアロケータとC++のnew

mruby Advent Calendar 2013 - Qiitaの19日目の記事です。

mrubyでカスタムアロケータを使う

mrubyを使うときは、まずmrb_open()を呼ぶわけですが、mrb_open_allocf()という独自のアロケータを指定できる亜種があります。

mrb_state* mrb_open_allocf(mrb_allocf, void *ud);

mrb_allocfは次のようにtypedefされています

typedef void* (*mrb_allocf) (struct mrb_state *mrb, void*, size_t, void *ud);

mrb_allocf()の引数ですが、2番目のvoid *はreallocする場合の元の領域へのポインタ(新しい領域ならNULL), size_tには確保したいサイズ(バイト数。0の場合は解放しろという意味)が渡されます。void *udはユーザデータでmrb_open_allocf()に渡したものがそのまま渡されて、自由に使えるようです。

では、何も考えずにmrb_open()を呼び出した場合はどうなっているのでしょうか?

mrb_open()は内部ではmrb_open_allocf()を読んでいるだけで、その際に最初の引数として内部で定義されているallocf()を渡しています。で、allocf()は次のようになっています。

static void*
allocf(mrb_state *mrb, void *p, size_t size, void *ud)
{
  if (size == 0) {
    free(p);
    return NULL;
  }
  else {
    return realloc(p, size);
  }
}

ふむふむ。素直にrealloc()とfree()を使っています。

で、まぁmrb_allocf()を自分で定義してmrb_open_allocf()に指定してやると独自のアロケータが使えるわけです。mruby内のあらゆるメモリ確保は独自のアロケータ経由になりますし、mrb_malloc()などのメモリ確保関数も内部的には独自のアロケータを呼んでくれます。

メモリの少ないシステムや、診断などをしたい場合に使えそうですね。

mrbgemでのメモリ確保

mrubyを使う側がカスタムアロケータを使う可能性があるので、いろんな人に使ってもらうmrbgemを書く際はメモリ確保にmalloc()を直接呼ぶのではなく、mrb_malloc()を呼ぶのが基本のようです。

mrbgemでC++のnewは?

んじゃmrbgemでC++のnew使うときはどうすんのよという話がでてきますが、placement new(配置new)を使えば良さそうに思えます。

C++のクラスFooをラップしたRubyのクラスFooをmrbgemで定義する例を書いてみます。あ、ちょうど2日前に構造体をラップする方法をtsaharaさんが解説されているので、mruby で C 言語の構造体をラップしたオブジェクトを作る正しい方法 - Qiitaを参考にします。

#include <new>

#include "mruby/class.h"
#include "mruby/value.h"
#include "mruby/data.h"

class Foo;   //どこかで定義されていることにする

void foo_free(mrb_state *mrb, void *ptr){
  Foo *foo = (Foo *)ptr;
  foo->~Foo();   //デストラクタを明示的に呼び出し
  mrb_free(mrb, foo); //メモリ解放
}

//Fooのタイプ定義
const static struct mrb_data_type mrb_foo_type = { "Foo", foo_free };

mrb_value foo_init(mrb_state *mrb, mrb_value self){
  void *p = mrb_malloc(mrb, sizeof(Foo));
  Foo *newFoo = new(p) Foo();  //placement newを使ってFoo::Foo()を呼ぶ
  DATA_PTR(self) = newFoo;  
  DATA_TYPE(self) = &mrb_foo_type;  
  return self;
}


void
mrb_mruby_foo_gem_init(mrb_state *mrb)
{
  struct RClass *fooClass;

  fooClass = mrb_define_class(mrb, "Foo", mrb->object_class);
  MRB_SET_INSTANCE_TT(fooClass, MRB_TT_DATA);
  mrb_define_method(mrb, fooClass, "initialize", foo_init, MRB_ARGS_NONE());
}

void
mrb_mruby_foo_gem_final(mrb_state *mrb)
{
}

placement newで本当に大丈夫?

Fooが単純なクラスの場合はこれで良さそうです。実際mruby-arduinoではArduinoのServoクラスをこの方法でラップしています。

でもFooの実装の中で普通のnewを使っている場合には対応できませんね。。複雑なライブラリでnewしまくっている場合やSTLまで使われている場合はどうすりゃいいんでしょう?

そのような場合はoperator newをオーバーロードしてやれば良さそうです。ただ、そうすると今度はmrubyを含むプログラムのnewが全部置き換わっちゃうので、それはそれで問題かも。うーむ。

2013-08-10 STM32F4DICOVERY+Wi-Fiモジュール+mrubyでWebサーバ

STM32F4DISCOVERYでmrubyを動かせましたが、次になにかしたいなと思っていたところ、簡単にマイコンに接続できるWi-Fiモジュールの存在を知りました。

GAINSPAN GS1011シリーズ

http://www.sugakoubou.com/doku/lib/exe/fetch.php?hash=2c0bd5&media=http%3A%2F%2Fwww.sugakoubou.com%2Fstore%2Fimages%2Froboba023.jpg

このモジュールは、マイコンシリアル通信上でATコマンドを使って簡単にWirelessの設定、TCP/IP通信ができるというものです。菅工房さんのページに詳しく解説されています。

菅工房 低消費電流 Wi-Fiモジュール

ATコマンドの詳細なリファレンスの入手には本来NDA契約の手続きが必要で、自分は契約書のコピーをファイルで送ったりしたのですが、うんともすんとも連絡が来ないので、WebでPDFを見つけて来ました^^;

で、このGS1011とSTM32F4DISCOVERYを接続し、簡易Webサーバを書いて基板上にある4色のLEDを制御してみました。

実装

GS1011とのシリアル通信を含むTCPライブラリをCで書き(かなり適当)、その上にこれまた適当なWebサーバをC/mrubyの混合ででっち上げ、その上のWebアプリ(?)とLEDの制御をmrubyで書きました。

一番上のWebアプリ的なものはSinatraライクに書けるようにしてあります。こんな感じ。多少はRubyらしいでしょうか?

include RouterDSL  #sinatraの超簡易版 get()
include Arduino     #mruby-arduino。pinMode()やdigitalWrite()

#各LEDのPIN番号
LED_GREEN = 60 
LED_ORANGE = 61
LED_RED = 62
LED_BLUE = 63

#LEDの初期化
[LED_GREEN, LED_ORANGE, LED_RED, LED_BLUE].each do |led|
	pinMode(led, OUTPUT);
end

html = <<EOS
	<html>
		<head><title>STM32F4-Discovery LED Controller</title></head>
		<body align=center>
		 <p>Hello mruby Wifi Web Server</p>
		 <li><a href="/control/green/on"> green on</a></li>
		 <li><a href="/control/green/off"> green off</a></li>
		 <li><a href="/control/orange/on"> orange on</a></li>
		 <li><a href="/control/orange/off"> orange off</a></li>
		 <li><a href="/control/red/on"> red on</a></li>
		 <li><a href="/control/red/off"> red off</a></li>
		 <li><a href="/control/blue/on"> blue on</a></li>
		 <li><a href="/control/blue/off"> blue off</a></li>
		</body>
	</html>
EOS

get "/" do
	html
end
 
get "/control/:color/:onoff" do |color, onoff|
	pin = case color
		when "green"
			LED_GREEN
		when "orange"
			LED_ORANGE
		when "red"
			LED_RED
		when "blue"
			LED_BLUE
		else
			return "I don't have color:#{color}"
		end

	if (onoff == "on")
		digitalWrite(pin, HIGH)
	elsif (onoff == "off")
		digitalWrite(pin, LOW)
	else
		return "Bad control:#{onoff}. should be on or off"
	end

	html
end

HTTPヘッダの解析なんかも正規表現ライブラリmasamitsu-murase/mruby-hs-regexpを使わせてもらってかなり楽ができました。

で、ブラウザからアクセスするとこんな感じのページが表示されます。

f:id:kyab:20130810114601p:image:w360

"green on"をクリックすると緑色のLEDが点灯、"orange off"をクリックするとオレンジ色のLEDが消灯、、といった感じです。この基板は青色LEDが綺麗です。

メモリ

で、本当はLEDの状態に合わせてHTMLも変えたいのですが、文字列を色々操作しているとRAM不足に陥りました。

ただ、STM32F4DISCOVERYのもう一つのメモリ空間(64KB)がほとんど余っています。ここにベースとなるHTMLテンプレートを置いたり、mrubyの初期化で確保している大きめの領域(20kb程度)を置けばまだいける!はず?