K.Sasada's Home Page

Diary - 2016 June

研究日記

水無月

_30(Thu)

Tokyo.ex #3 で宣伝してきた。

_29(Wed)

昨日の続き。

色々整理して、

                        trunk   modified
vm1_lvar_set_object0*   1.937      2.280
vm1_lvar_set_object1*   1.995      2.081
vm1_lvar_set_object2*   2.025      2.230
vm1_lvar_set_object3*   1.978      2.461

こんな感じに。まぁ、これくらいなら良いかなぁ。


ep の需要が高まってきているので、ep をレジスタにするべきか否か。

_28(Tue)

Proc を WB protected にしようとしている。といっても、実は Proc じゃなくて、Proc から参照される Env(ローカル変数のストレージ)を WB protected にしようとしている。

Env と ENV は違うので注意。RubyVM::Env という隠しオブジェクト。 ちなみに、こいつは 2.4 では見えなくする予定。

そうすると、

a = Object.new

みたいな取るに足らないコードも、実は Env への書き込みの可能性があるので、

env[:a] = Object.new

のように扱わねばならない。

具体的には、MRI では、Proc が作られると、その Proc オブジェクトが参照可能なローカル変数を Env オブジェクトに追い出す。こんな感じ。

def test
  a = 1 # まだ、スタック上にいる
  Proc.new{
    p a
  } # この時点で、変数 a の寿命はメソッド test の寿命より長いんで、
    # a は Env (e としよう)にコピーされる
  a = Object.new # e[:a] = a のような挙動になるので、
                 # write barrier が必要
end

こんな感じ。

ローカル変数アクセスをするごとに、「これはスタック上か? スタック上じゃなかったら Env へのライトバリアが要るよね?」ってコードになるので、ローカル変数のアクセスが遅くなる。

# vm1_lvar_set_object0
def test
  i = 0
  obj = Object.new
  a = b = c = d = e = f = g = h = j = k = l = m = n = o = p = q = r = nil

  while i<30_000_000 # while loop 1
    i += 1
    a = b = c = d = e = f = g = h = j = k = l = m = n = o = p = q = r = obj
  end
end

test

こんなコードを、trunk と今回 hack した結果と mruby の実行時間(秒)を並べておく。空の whileloop の実行時間は消してあるので、純粋に代入だけと考えれば良い。

                         trunk   modified        mruby
vm1_lvar_set_object0*    1.961      2.004        2.622

trunk と modified では、若干遅い。 mruby は、それらより遅い。

このケースでは、test メソッドの Env は生成していなかったので、スタックに書き込むだけ、となっている。なので、「ローカル変数はスタック上にあるのかな?」というチェックのオーバヘッドで遅くなっている。


次は、Env が生成されるパターン。

# vm1_lvar_set_object1
def test
  i = 0
  obj = Object.new
  a = b = c = d = e = f = g = h = j = k = l = m = n = o = p = q = r = nil
  Proc.new{}
  while i<30_000_000 # while loop 1
    i += 1
    a = b = c = d = e = f = g = h = j = k = l = m = n = o = p = q = r = obj
  end
end

test

途中の Proc.new{} した時点で、test のローカル変数は Env に待避される。 常識的に考えれば、Proc.new{} からは、test のローカル変数を参照してないんだから、 別に待避させなくてもいいんじゃね、という気分になるけど、Proc#binding という機能があるので、a とかにアクセス出来ちゃう。(see also lambda lifting)

また、さらに言うと、常識的に考えれば Proc.new{} は誰も参照していないんだから、 そもそも要らねーだろ、という気はするが、世の中には TracePoint というものがあって、返値を capture することで、escape が出来るという... なんというか、色々つらい話があります。

そこまでやるのは病的だろう、という感じもしますが(それについては同意します)、現状ではこうなっている。まぁ、実際のところ、色々例外ケースを考えるのがしんどいので現状の、とにかく無差別に作る、というようなことをしている。

さて、結果。

                        trunk   modified        mruby
vm1_lvar_set_object0*   1.961      2.004        2.622
vm1_lvar_set_object1*   1.996      3.313        2.567

予想通り、modified が、遅くなっている。 「ローカル変数は Env 上にいるか? お、要るな。つまり、Env -> obj への参照が生成されたぞ、つまり、write barrier が必要だ」となって、WB のオーバヘッドがかかっている。「嘘、私の WB 重すぎ?」ってな感じです。

代入、1.5 倍くらい遅くなってますね。いいかなぁ、これくらいなら。どうなんだろ。代入、どれくらいします?

(口調が変わる)

面白いのは、mruby は速度がほとんど変わっていないことです。これは何故かというと、mruby はメソッドから抜ける、ローカル変数を Env(相当)に待避させません。メソッドからから抜ける(この場合は、test メソッドが終わる)タイミングで、はじめてローカル変数の領域を確保します。

この方法の良い点はいくつかありますが、(1) 見てわかるように WB が不要になる (2) スタック領域を使い続けるので、メモリ効率が若干よくなる、といったものがあると思います。

欠点は、リターン時に待避するかどうかのチェックが必要になります(see also return barrier)。また、MRI の現在の構造だと、その環境を参照している Proc が参照しているポインタを、リターン時にすべて書き換える必要があります、が、これは Env を必ず経由するようにすれば解決するからどうでもいいな。

いや、MRI でもそれくらいやってもいいんじゃね、という気はしますが、 本当に動くかちょっと不安。メリットは、多少のメモリ効率の向上でしょうか。


さて、mruby は WB が不要かというと、そうでもないです。

# vm1_lvar_set_object2
def test
  i = 0
  obj = Object.new
  a = b = c = d = e = f = g = h = j = k = l = m = n = o = p = q = r = nil

  1.times{
    while i<30_000_000 # while loop 1
      i += 1
      a = b = c = d = e = f = g = h = j = k = l = m = n = o = p = q = r = obj
    end
  }
end

test

ここでは、ブロックの中から、外の変数に代入しています。

結果。

                        trunk   modified        mruby
vm1_lvar_set_object0*   1.961      2.004        2.622
vm1_lvar_set_object1*   1.996      3.313        2.567
vm1_lvar_set_object2*   1.948      2.019        9.595

modified は遅くなっていませんが、mruby が遅くなっています。

MRI では、1.times ... のようなことをしても、Proc(Env)を作りません。なので、Proc 生成を陽に行なわない、ブロック呼び出しが速いです(ここでは関係ないですが)。 しかし、mruby では、Proc/Env を生成することになります。

そして、ブロックの上の方のローカル変数へ代入するときは、スタック上にあろうと無かろうと、とにかく WB をすることになっています。

def foo
  a = 1
  Proc.new{ a = Object.new }
end

foo.call

こんなケースを考えて貰うとわかるのですが、a = Object.new としたタイミングでは、メソッド foo のフレームは存在しないため、VM のスタックを参照しても、生成されたオブジェクトへ辿り着くことができません。もし、Env が old だった場合、生成したオブジェクトはマークされなくなってしまいます。そのため、mruby では、WB をやっているんだと思います。


が、まだスタック上にあるんだから、今回の場合は、別に WB 要らないよね。

というわけで、スタック上にある場合は、WB しないようにしてみました。

index 6f8c510..2c15c48
--- a/src/vm.c
+++ b/src/vm.c
@@ -972,7 +972,9 @@ RETRY_TRY_BLOCK:
         mrb_value *regs_a = regs + GETARG_A(i);
         int idx = GETARG_B(i);
         e->stack[idx] = *regs_a;
-        mrb_write_barrier(mrb, (struct RBasic*)e);
+
+       if (!MRB_ENV_STACK_SHARED_P(e))
+         mrb_write_barrier(mrb, (struct RBasic*)e);
       }
       NEXT;
     }

1行加えただけ。

                        trunk   modified        mruby         mruby.modified
vm1_lvar_set_object0*   1.961      2.004        2.622
vm1_lvar_set_object1*   1.996      3.313        2.567
vm1_lvar_set_object2*   1.948      2.019        9.595
vm1_lvar_set_object2*   1.966      2.014        10.122           7.387

あれ、あんまり速くなんないですね...。環境辿るのが遅いのかな。 まぁ、ちょっと改善しました。


MRI も、別にローカル変数の代入するたびに、毎回 Env 取り出して WB する、みたいなことをしなくても、

  • コントロールフレームスタックから参照される Env については、VM(スレッド)のマーク時に mark する
  • leave 時(メソッドが返るとき)、ep が Env を参照してたらその Env をリメンバーセットに入れておく

ということで対処可能です。どーしよっかなぁ。 ちょっと、工夫は必要になるが。


おまけ。 vm1_lvar_set_object1 では、Env は若いままなんで、WB やっても、「若いオブジェクト→...」という参照では、WB は最終的に無視されます。

そこで、Env を古くしてみましょう。

# vm1_lvar_set_object3
def test
  i = 0
  obj = Object.new
  a = b = c = d = e = f = g = h = j = k = l = m = n = o = p = q = r = nil
  Proc.new{}
  GC.start; GC.start; GC.start # make env old

  while i<30_000_000 # while loop 1
    i += 1
    a = b = c = d = e = f = g = h = j = k = l = m = n = o = p = q = r = obj
  end
end

test
name                    trunk   modified        mruby   mruby-modified
vm1_lvar_set_object0*   1.962      1.944        2.599            2.686
vm1_lvar_set_object1*   1.944      3.241        2.595            2.646
vm1_lvar_set_object3*   1.974      4.001        2.600            2.679

ちゃんと、WB 追加で modified が遅くなっているのがわかります。 しかし、WB が不要なのに、その判断のために遅くなっているやつがつらいですね。色々理由はあるんですが、ここを解決できればいいんだけど、incremental marking 中であることがわかればなぁ。


今回の結果のまとめ。

name                    trunk   modified        mruby   mruby-modified
loop_whileloop          0.579      0.596        1.471            1.492
vm1_lvar_set_object0*   1.962      1.944        2.599            2.686
vm1_lvar_set_object1*   1.944      3.241        2.595            2.646
vm1_lvar_set_object2*   1.965      2.006        9.578            7.415
vm1_lvar_set_object3*   1.974      4.001        2.600            2.679

MRI は loop 速いね!


最近、昼の予定なんてなかったので油断していたら、久々にやらかしてしまった。

ご迷惑をおかけして大変申し訳ありません。

_27(Mon)

かなり切羽詰まっている。

Brexit という単語が流行ってるね。 もう株価を確認するのが怖くて見てません。つらい。

_26(Sun)

ずっと原稿。


なんか、アイリッシュバンドの演奏というモノを聞いた。 財布の中におかねがなくて、ギリギリだったが、500円玉があったので、投げ銭。

_25(Sat)

証明とかの記号がさっぱり読めないので、誰か手取り足取り教えて欲しい。

(何を読めば良いのか...)


ずっと原稿。

_24(Fri)

少し人と話をしただけで、気力が尽きる。 つらい。

色々勉強したいのだが。


今更、ローカル変数の実装とかを色々と弄っている。

_23(Thu)

そういえば、先日 laptop を新調するか凄い悩んで、結局先送りすることにした。

モチベーション

  • もう3年以上たったし...。
  • 小さくなるといいな
  • 速くなるといいな
  • 冷たくなるといいな(今の laptop はすぐに熱くなって、凄いファンがうるさい)

Let's note を10年以上使い続けている(もうそんなになるんか)ので、今度は SX5 かと思って調べてみても、

  • CPU が i7-6500U だと TM が使えない(使うかわかんないけど...)
  • スペシャルエディションみたいなので、i7-6600U があったけど高い
  • 16G 高い

ThinkPad で20万くらいのが、Let's note で40万、みたいな価格設定で、どうにも悩んでしまって、結局、現状のまま、もう少し待つか、というところに。


で、この Let's note NX2 をもう少し使い続けるために、ウェブでてきとうに検索して、次のような強化を行なった。

  • メモリ 8GB -> 16GB へ
  • Wifi モジュールが変えられるらしいので、Intel Dual Band Wireless-AC 7260 に替えた

AC に対応してなかったので、家の AP はてきとうな奴だったけど、せっかく AC 対応になるので、良い奴を買った。

  • WXR-1900DHP2

ネットワークむっちゃ速くなった。快適。色々、ネットワークの構成もシンプルに(Wifi の中継器が不要になったり)。

しかし、夜中の4時くらいに、update が走ったのか、急にネットワークが切れたつらい。

NX2 には、バッテリーが軽い奴と重い奴があって、軽い奴をずっと使っていたら、すぐに寿命が来てしまったので、最近はずっと重い奴を使い続けている。重いだけに、10時間くらいもつので便利。軽い奴は30分程度くらいしかもたないくらいだった。のだが、軽いのは、やはり軽い(重い奴に比べて 200g くらい軽い)ので、新しく発注した。

のだけど、メーカー在庫無し、と言う連絡がきてしまった。もう少し頑張ってくれ Panasonic。


slack team を2つ見ていたくなったのだけど、ウェブ経由だと、2つページを開いていないといけないぽいので、しょうがないから slack for windows をインストール。一体何個メッセージアプリがあるんだ。ほんとつらい。

  • IRC (LimeChat)
  • Twitter (OpenTween)
  • Hipchat
  • Slack
  • Facebook (web app)
  • idobata (web app)

つらい。

_22(Wed)

そういえば、先日、東京11の支払いを、だいたい終わらせた。 予算規模が600万とか、ちょっと個人が扱う額じゃないよね...。 税務署に相談に行ったりして大変でした。

今回の経験は、今度どっかにまとめたい。

_21(Tue)

たまっていた日記を 5/2 からずっと付けてた。 付けてたって言うか、とにかく埋めたというか...。

最近、えらい暑いね。


近所の図書館にラノベが大量に置いてあって、読みまくってるんだけど、何をどこまで読んだかすぐ忘れる(デュラララ!! ってどこまで読んだんだっけ)。

なにか記録するものを使った方がいいような気もするが、はて。

_20(Mon)

焼きそば食べた。

_19(Sun)

何してたんだろ。

_18(Sat)

新宿で買い物など。 ゴジラが居るなんて知らなかった。

映画、予約しないで行ったら満席で残念...。

_17(Fri)

豊洲へ。

_16(Thu)

機械学習の勉強会にいくために渋谷の dots へ。

Apple store に、2011年の mac book air を持っていってバッテリー交換を依頼。1日でできるのね。

_15(Wed)

型の話で電話会議。ちょっと電話会議多すぎないか?

_14(Tue)

SciRuby 回りの電話会議。


東京Ruby会議11 でお世話になった方々(で、お呼びできた方々)と打ち上げ。

_13(Mon)

Ruby開発者会議@マネフォ。 その後、四谷三丁目へ行ってハンバーグ食った。

_12(Sun)

何も覚えていない。

_11(Sat)

何も覚えていない。

_10(Fri)

RA 理事会。

_9(Thu)

何も覚えていない。

_8(Wed)

電話会議。

_7(Tue)

GSoC の話を伺う。

_6(Mon)

続京都食べ歩き。

_5(Sun)

京都食べ歩き。

_4(Sat)

何も覚えていない。

_3(Fri)

豊洲。

_2(Thu)

ザックと素麺を食べる。

_1(Wed)

何も覚えていない...。

銀行振り込みしようと思ったけど、振り込み先を忘れるというミスをしていたようだ。

Sasada Koichi / ko1 at atdot dot net
$Date: 2003/04/28 10:27:51 $