K.Sasada's Home Page

Diary - 2005 January

研究日記

睦月

_31(Mon)

腰が痛い.


そういえば,エディタネタで言うと,対応する括弧で挟まれた部分全部が括弧にカーソル置いたとき,強調とかされないとプログラミングできないなんじゃなくな体になっております.

だから,ふつーの設定のエディタではプログラミングできません.具体的には自分の環境の xyzzy じゃないと書けません.弱.


ある人に,D2 だから就職活動始まるよね,といわれた.そうなんだよなぁ.何か活動しないと.


研究ネタを書くのはそろそろやめようかと思っていたのだけれど,もうちょっと.


私の研究はまだ無いプロセッサの上でどんなふうにソフトウェアを作るか,なので,主に評価はシミュレータを使います.チップを作るのは大変すぎるので(作ってる人は居る).チップを作ろうとすると数千万とかかかるので,ふつーの大学では作れません.作ってる人は FPGA を使って作ります.


さて,シミュレータなので,本物のハードウェアで作れネーよ,ということも簡単に追加できるため,ソフトウェアしかやっていない私には,「こんな命令ほしーなー」と思っても,それを載せるのは非常にためらいがあります.ソフトウェアには簡単なことでも,ハードウェアには非常にコストがかかることがままあるからです.

しかし,逆にソフトウェアでは大変なことでも,ハードウェアでは簡単にできることもあって,なかなか侮れません.


まぁ,そんなわけで,今はある命令を作って評価してるんですが.

バグが見つかりません.

まぁ,シミュレータを弄っているんで,多分そこかなーと思ってデバッグしますが,取れません.大変です.


一生懸命探すと,やっとわかりました.シミュレータじゃなくて,その上で動作させているバイナリにバグがありました orz


ハックするのは一箇所がいいよね,とか思うこのごろ.YARV のデバッグでも同じようなことはままありますが.


というわけで,就職活動をしてみる.

一番上にあると邪魔な気がするな.


今年のシステム製作実験も面白かった.ダメダメなのもあったけど,いいのもあった.見る側も,きちんとしていないといけない.

_30(Sun)

コピペな(人の)ソースを弄る.とてもつらい.目が霞む.


うりゃ,っと MIPS の命令を追加しようとしたら,なんと命令追加する隙間がない.どうしようかなぁ.使っていないのをてきとうに潰してしまえ.

64個というのはすぐに枯渇するものなんだなあ.

MIPS っぽいけど MIPS じゃない何か,ということで許してもらおう.


バグが取れたら帰ろうと思いつつ,全然取れん.


昨日の解答。

というか、先日の論文にあったんだけどさ。

each thread:
  for(i=0; i<N; i+=ThreadNum){
    t = independent_computation();
    LOCK(lock_var[thread_id]);
    A[i+1] = A[i] + t;
    UNLOCK(lock_var[next_thread_id]);
  }

最初、なんでこれでうまくいくのかわからんかった。lock_var は、一個以外全部ロック状態ではじめる必要がある。私は並列化初心者なんです。駄目じゃん。

並列ソフトウェアを作る基盤はいろいろやってるような気がするんだけど、並列ソフトウェア自身を作ったことがないので、いつもサンプルに困る。

並列化を阻害する要因は、independent_computation() の計算量が少ないと、相対的にロックの負荷がかかる。つまり、「並列化したほうがよいか,このまま実行すべきか」は、ロック(並列化にかかるオーバヘッド)よりも、independent_computation() を並列実行する高速化が勝っていれば、並列化するべきである。(単純だ。アムダールの法則とか有名)

で、こういうのの粒度(独立にできる計算の大きさ)を頑張って大きくしましょう、というのがたいていの並列化の研究。

私は、ちっちゃくてもメリットがあるようにがんばりましょー、という研究。

ちなみに、「細粒度」と言っても、その単位は人によってだいぶ違う。数命令から数千命令、数プロセスとか。


プロセッサはマルチコア、マルチスレッドに必ずシフトします。そのためのシステムソフトウェアの研究は、プロセッサアーキテクチャの研究に比べてあまりありません。従来の疎粒度並列計算機のモデルをそのまま適用して喜んでるだけです。

私の研究は、そんなところをやってます。


という建前。

ある意味隙間。

というか、もうシフトしてるよね。10年前に上記を言っているとかっこよかったんだけど。

_29(Sat)

ipsj全国大会のプログラムが発表される.座長を見てみる.うぅ,てきとーな発表できないな,こりゃ.

いや,別に人によっていい加減な発表をしてもいい,というわけではないのですが.


ruby の起動を乗っ取るのはどうすればいいかなー,なんて思っていて,nodedump だと trace_func 使ってるしいろいろと面倒だなー,rb_eval 乗っ取らなきゃ無理かなー,とかいろいろと考えていたんだけれど,もっと単純だった.

# ite.rb
require 'yarvcore'
YARVCore::eval(IO.read($0), $0, 1)
exit

こう使う.

ruby -rite program.rb

他の -r には対応していない.頑張ってプログラム中に書いてください.


wikiばな vol.4 というのは 3/12 かー.どうしようかな.一回行ってみたかったんだけど.というか,かずひこさんすげー.このために東京に来るのかしらん.


ちなみに,gcc で,asm 文で ll/sc を使おうとすると,こんなふうになります.

#define TH_LOCK_SPIN(lock_addr)                     \
{                                                   \
  asm volatile(                                     \
                ".set noreorder\n"                  \
                "1:;"                               \
                "ll   $2,(%0);"                     \
                "bnez $2,1b;"                       \
                "li   $8,1;"                        \
                "sc   $8,(%0);"                     \
                                                    \
                "beqz $8,1b;"                       \
                "nop;"                              \
                "2:;"                               \
                ".set reorder\n"                    \
                                                    \
                : /* output */                      \
                : "r" (lock_addr) /* input  */      \
                : "$2","$8" /* broken register */   \
                );}

#define TH_UNLOCK_SPIN(lock_addr) \
  *lock_addr = 0;


volatile int shared_lock; /* ノート */
int shared_data; /* 鍋     */

/* 各スレッドでやる */
func(){
  TH_LOCK(shared_lock);
  shared_data をホゲホゲする.
  TH_UNLOCK(shared_lock);
}

for(i=0; i<N; i++){
  A[i+1] = A[i] + independent_computation();
}
  1. 上記を並列化せよ.
  2. 上記を並列化するときの性能を阻害する要因を述べよ.
  3. 上記は並列化したほうがよいか,このまま実行すべきか述べよ.
_arton(Sat Jan 29 15:57:43 JST 2005)

 これ、「痛」じゃなくて、もちろんrを前置するための洒落ですよね? と野暮な質問してみたり。

_ささだ(Sat Jan 29 16:50:45 JST 2005)

 いてててて.

_28(Fri)

たまには自分の研究のことも書いてみる.いつも Ruby をやってると思われると困るからだ(誰に?).


並列処理を行う研究者は,同期や排他制御について関心がある.まぁ,排他制御を行うことができれば,同期(バリア同期など)はソフトウェアで作ることができるので,とりあえず排他制御を効率的に行う方法を考えてみる.


かなーり簡単なところから説明してみる.

並列処理を行うってことは,実際に計算する「何か」が複数無いといけない.たとえば,人が二人いれば,その二人で同時に何か仕事をすれば,それは「並列」処理になる.もちろん,計算機の分野で扱うのは,「人」ではないわけだけど.その「何か」とは,この際置いといて,「人」で考えてみる.

N 人で共同作業を行うとき,あるひとつの共有資源があって,それを使うのは常時ただ一人でなければならない,という場合がある.こんな抽象的な言い方じゃよくわからないので,たとえば仕事は「料理」,共有資源は「鍋」と考えてみる.

「2」人で一緒に「料理」をしているとする.それぞれ,並列に作業するが,「鍋」はひとつしかない.さて,このときどうやって仕事を進めていくかはいろいろな方法がある.たとえば,一人は包丁使う作業をすべて行い,一人は「鍋」を使うことを行う.こうすれば,仕事ははっきりと分かれるが,多分,あんまり効率的じゃない.

料理が2品あったとき,料理をそれぞれが作る,というのは結構いいアイデアである.ただし,「鍋」を両方とも使う必要があったとき,その「鍋」の占有権を取る必要がある.たとえば,片方が「カレー」を作っているところに,もう片方が「シチュー」を鍋を強引に奪って(それとも,混ぜ混ぜにして?)料理を続ければ,結果として「カレー」と「シチュー」を得ることは出来ない.

この場合は,仕事の分け方が悪かったんだろう.「鍋」を使う料理をやる人(カレーとシチューを作る係)と,「鍋」を使わない料理をやる係(サラダやデザート製作)に分けてやれば,きっとこんな悲劇は起こらなかったはずだ.

えーと,なんか話が全然別の方向にいってしまった.でも,共有資源をどうやって使うか,管理するか,っていうのは並列・並行処理の基本である.

たとえば,「鍋」を使うには,ノートに記帳してからでないと使えない,とすると,混乱は避けられる.返すときは,返した旨をそのノートに記帳すればいい.もし,ノートに「使用中」と書いてあれば,誰かが返してくれるまで待つ.

使っている人は,「鍋」の占有権を得られるので,その人は「鍋」に対して好き勝手出来る,他の人には邪魔されないということが保証される.こんなのをアトミックな操作という,と思う.多分.

さて,「待つ」という動作にしてもいくつか方法がある.ひとつは,「鍋」を返してくれるまで,ずーーっと鍋置き場(鍋貸し出し帳でもいいや)を見張っている方法である.ずーーっと見張っていれば,きっと誰かが返してくれるので,そのときにすかさず自分の占有権を得るのである.誰にも迷惑はかけないし,返されたときにさっさと自分のものにすることはできるが,自分はずーっと見張っていないといけないので面倒である.

こういうのを計算機でやるときには「スピンロック」や「ビジーウェイト」という.プログラムで書けば,

while(鍋取ってくる() == だけど取れなかった){ /* */ }

こんなふうになるので「ループ」してるから.

さて,待っている人数が増えてきたらどうなるだろう.500人くらいが鍋の返却を待っていたら,その部屋は大混雑だ.

これを解決するのは簡単である.「待っている人一覧」表を作ればいい.「鍋」がほしい人は,まだ「鍋」が返ってきてなければ,「待っている人一覧」に,自分の名前を書いてぼけーっとしておく.いや,TVやマンガを見ていてもいいけど,まぁ待っておく.料理の進められる部分を勧めてもいい.

「鍋」を返そうとした人は,まず「待っている人一覧」を見る.もし誰かが待っていることがわかれば,その一番上の人の欄を消して,その人に対して「返したよ〜」と伝えてあげる.そうすれば,効率よく「鍋」の利用権を渡してあげることができる.

こういうのを計算機の分野では「ブロッキングロック」とか言う.この方法だと,いろいろ楽なようだけれど,「待っている人一覧」に何か書かなければいけないし,少し面倒くさい.たとえば「待っている人一覧」を誰かが破って捨ててしまったらどうなる?

まぁ,そんな感じで手法はいろいろあるけど,メリットデメリットがある.工学ってのは,そういうところのバランスを考える仕事.

たとえば,「鍋」があと5秒で帰ってくるのなら,ずーっと待っていたほうがいいだろう.だけど,5時間後に帰ってくるんだったら,ずーっと待っているのはバカみたいだ.

OS など作ってる人たちには,「じゃぁどっちにしよう」と適切に選ぶ方法を考えている研究などがあったりする.90年代前半までくらい?


ちなみに,「鍋」が「複数」ある場合はこういう資源管理手法を「セマフォ (semaphore)」とか言ったりする.「一個しかない」ときは,「バイナリセマフォ (binary semaphore)」,または「ミューテックス (mutex)」とか言う.多分.


さて,計算機で考えてみたとき,「鍋」は何にあたるんだろう.「人」は何にあたるんだろう.

まず,「人」について.まぁ,ちょっとでも計算機を知っている人からは「そりゃー CPU でしょー」というに違いない.つまり,並列計算とは「CPU を複数使って並列に行う計算」と定義できるわけだ.

えーと,大体正しいし,世の中の並列計算の大部分は上の定義で間違いない.秋葉原に行けば,dual CPU な計算機は10万も出せば買える.パソコンを複数台つないで作る「クラスタ計算機」は,s-coreMIKO GNYO/Linux とか使えば簡単に構築できるし(ごめん,それぞれ使ったことがないので簡単かどうかは知らない),今流行の「GRID」はこれをもーーっと大規模にしたもので,数千台規模の計算機を使って並列に計算しよう,というものだ.

「大体」って言ったからには例外があるんだろう,ということなんだけど,もちろんある.今はひとつの CPU の中で「並列」に動作するものがある.これについての詳細は省略するけど,ひとつの CPU の中に,複数の Core が入っているもの「とか」がある.なので,CPU 一個と言っても,そいつが「並列」処理ができるかもしれない.一番メジャーなものだと,Intel の Hyper Threading という(商業)名の,正式には Simultaneous Multi-Threading (SMT) という名前の技術なんだけど,そういうのがある.

で,その SMT が私の研究分野なんだ.

今言った,「チップ内並列」から,「複数 CPU 並列(SMP計算機が一般的)」,「複数台並列(クラスタ計算機)」,「複数台並列(GRID 計算環境)」など,いろんな並列計算機がある.この順番で計算単位の密度が「密」から「疎」になっている.

「密」なものは,「密」なので通信が速い.チップ内並列なんて,もう馬鹿っぱやである.だって,ひとつのチップ内なんだから.

GRID とかになると,もう見てらんないくらい遅い.だって,途中で鳩が飛ばしてるかもしれないんだから.


えーと,今日は同期の話をするんだった.

見てきたように,いろんな「並列処理」計算機があるわけだけど,その場合場合によって同期方法は異なる.1000人が一斉にひとつのノートを参照しようとしたら破綻するとか,そんな感じ.5人だったらノートでいいよね.


で,いきなり SMT プロセッサ上での同期を考えてみる.チップ内並列 CPU による並列計算機に置き換えても,結構正しいので,そう思ってもらっても構わない.SMT に依存する話については明記する.

こいつは「密」なので,それぞれの並列単位,面倒だからスレッド,と言おう,スレッド間で情報共有するのは比較的楽である.なので,「ノート」を用いて同期を実現してもあんまり問題は無い.


ここで,並列(並行)計算でどんなことを「同期」,具体的には「排他制御」しないといけないか考えてみる.

C 言語でいうと,i++ という計算があったとする.こいつは機械語のレベルで見れば

  1. 「lw $r, i_addr」
  2. 「add $r, $r, 1」
  3. 「sw $r, i_addr」

という3命令に分かれる(似非 MIPS命令語.それぞれ,レジスタ $r に i_addr のメモリアドレスの値を読み込み,$r = $r + 1,$r の値を i_addr に格納,の意味).

このとき,一番目の「lw $r, i_addr」を行ったとき,他のスレッドが上記3処理を行ったらどうなるだろう.

  1. スレッドA 「lw $r, i_addr」スレッドA の $r に i
  2. スレッドB 「lw $r, i_addr」スレッドB の $r に i
  3. スレッドB 「add $r, $r, 1」スレッドB の $r に i+1
  4. スレッドB 「sw $r, i_addr」i_addr には i+1 が格納される
  5. スレッドA 「add $r, $r, 1」スレッドA の $r に i+1
  6. スレッドA 「sw $r, i_addr」i_addr には i+1 が格納される

スレッド A,B がそれぞれ「i++」という処理をしたのなら,i の値は「2」インクリメントしなければいけないのに,最終的には i の値は i+1 になってしまった.困った.

上の「鍋」のメタファーで言えば,「鍋」は「i」へのアクセス権,ということになる.

要するに「i」へのアクセスは「保護」してあげなきゃいけなかったってことになる.そう,鍋の時にはノートでアクセス制限していた.じゃぁ,ノートを作ってあげよう.

spin:
if(note == true) goto spin; 
note = true;
i++;
note = false;

note が true のときは,i は誰かに利用されている,ということにしておく.note が true の間はスピンロックするようにしてみよう.さぁ,これで完璧だ.


だったら,いいんだけど,これでは済まない.if文を抜けたところでは note は false なので,このタイミングでは複数のスレッドがこの if 文を抜けることになる.

  1. スレッドA: if(note == true) goto spin; /* note は false なので抜ける */
  2. スレッドB: if(note == true) goto spin; /* note は false なので抜ける */
  3. スレッドA: note = true;
  4. スレッドB: note = true;

ようするに,スレッドA, B が両方とも「note は false」と思っているのがいけない.


では,どうするかというと,こういう場合は OS に頼ることもあるんだけど,OS に頼らないで,こういう場合専用命令というのを使う.x86 の命令はよく知らないので,MIPS の LL (load linked) / SC (store conditional) という命令を使ってみる.MIPS は,PS とかで使われてる CPU である.

if(node==false) goto spin; の代わりに,次の機械語列を実行する.

spin:
  ll   $r, addr # $r = *addr; load link with addr
  bnez $r, spin # if($r != 0) goto spin
  li   $r, 1    # $r = 1
  sc   $r, addr # store if store condition is ok with addr
  beqz $r, spin # if($r == 0) goto spin;

いきなり MIPS の機械語でアレだけど,まぁ注釈もあるし.さて,ll で load link ってなんだろう.これは,二つの動作をする.

  1. 監視開始フラグ(ll bit)を立てる
  2. 監視対象アドレス(ll addr)を addr にする

えーと,プロセッサは ll bit がたっていると,ll addr へのメモリアクセス(書き込み)を監視することになる.ll bit がたっている間に ll addr へメモリアクセスがあったりすると,ll bit を下ろす.ll bit が立っていなければ,sc は失敗する(書き込みは行われず,destination register に 0 が入る).

プロセッサとしては ll addr を直接監視するわけじゃなくて,ll addr の変数を格納したキャッシュエントリがコヒーレントプロトコルによって追い出されたときなどに ll bit を追い出すのだと思うのだけれど.

で,SC 命令だけは,あるメモリアドレスにおいてひとつのスレッドしか実行しないことが約束されているので,それだけは守ってもらえる.

これによって,i へのアクセスはアトミックに行えることになる(こういう,保護された部分をクリティカルセクションと呼ぶこともある).


えーっと,長かった.これが,基礎の前です.


さて,ll/sc を利用すればアトミックな読み書きが出来ることはわかった.あとは効率の問題.


ところで,ll/sc は test and set 命令っていうけど,test and test and set 命令ってのが,よくわからない.誰か知りませんか?


ll/sc を利用して,たとえば10スレッドが同じところでスピンロックしてると,どうなるか.みーんな spin lock すると,メモリアクセスが大量発生したりして大変.とくに,SMT だと,実行リソースを各スレッドで共有するのでもっと大変である.つまり,スピンロックしてる奴らは,クリティカルセクションしてるスレッドが終わるのを待ってるんだけど,スピンロックが資源を食いつぶしてしまうのでなんとかしなきゃいけない.

Intel Hyper-Threading では,pause という命令を利用してこの状況を変えようとした.つまり,spin 中に pause 命令を実行すると,プロセッサが一定期間止まって,他のスレッドに実行を譲る,という仕組み.ひじょーにわかりやすい.つまり,「ゆっくりスピンループ」ということになる.ゆとり教育とかと同じですね.いや,全然違うか.

これは,ひじょーにわかりやすいし,多分効果はあるんだと思うけど,やっぱりそんなに単純じゃない.

どれくらいの期間休めばいいの? とか,その辺.


じゃぁ,っていうんで,Intel は Prescott において,monitor/mwait という命令を作った.こいつは,ある命令を監視して,その命令へのアクセスがあったとき(とか)に mwait の実行を再開する仕組みである.

通常,ロックは共有変数を利用して行われるので,その共有変数のロックが解除するときには,当然その共有変数に対して(たいていは)0 が書き込まれる.

で,そのときまで眠っていれば,阻害することはないはずである.うーん,頭がいい.

でも,問題が無いわけじゃない.

Intel HyperThreading では,2個スレッド同時実行だけれど,2個以上あったらどうなるんだろう.同じロックに対して,3個とか7個のスレッドが待っていたらどうだろうか.ロックを解除した瞬間に,すべてのスレッドが起き上がって取り合いになってしまう.ちょっと悲しい.


世の中には頭のイイ人がいて,やっぱりもっと効率のいいロックを提案している人がいる.

Supporting Fine-Grain Synchronization on a Simultaneous Multithreaded Processor という論文に詳しいのだけれど,ハードウェアに lock box という,さっき ll/sc で書いたようなロックをハードウェアで実装してしまおう,という機能をくっつけよう,って書いてある.

こいつの凄いところは,同じロックで待っているスレッドがあった場合,その中のひとつに対してロックを解除してあげましょう,ということである.また,(これが一番凄いんだけど)そうやってロックを解除するときには「共有変数に対して解除情報を書き込まないでいい」ということ.つまり,ロックの権限の委譲をハードウェアが保証しているので,わざわざメモリに書き込みに行かなくてもいい,ということ.

これはイイ.何がいいかって,メモリに書き込みにいく回数が結構減る(可能性がある).細粒度処理,つまり,ロックがいっぱいかかっちゃうような処理というのは,こういうロックのオーバヘッドを嫌ってあんまり作られないんだけど,これを利用すれば,ずっと速いものができるかもしれない.

というわけで,これは凄い.


ちなみに,full/empty bit をメモリやレジスタに付けるって研究もあるけど,そもそもメモリに付けてしまうとメモリアクセスが発生していや(具体的には,キャッシュコヒーレントプロトコルが走るだけだと思うが,バスは食う)だし,CPU 内共有レジスタによるロックは速いんだけど,スケーラブルじゃない(つまり,複数 CPU とかに拡張できない.だって,チップ内限定なんだもん).


ちなみに,lock-box を用いるロックにも欠点があって,たとえばロック獲得命令 Aquire は,メモリのフェッチアンドモディファイ命令になっている.全然 RISC っぽくない.その辺は回路をうまく作るんだろうけど,それなりに複雑化する予感.

他にも,バリア同期にそのまま適用できないような気がするので,もうちょっとなんとかしたい.

なんてことを,2/7 までに考えなきゃいけないわけです.うーん. いや,考えて,実装して,評価とって,原稿書かないと.うーん.


しかし,full/empty bit をメモリに付ける方法の弱点が思いつかんな.ハードウェアが大変になりそう?


ちなみに,こんなことを考えている人は何人くらい居るんだろう.ruby といい,やっぱりマイナーなことしかしてないな,俺は.


などと,煮詰まった上に夜中に起きたので書いてみた.


地震だ.


朝飯食ったらなんとなくアイデアが浮かんだ.どうじゃろうなぁ.個人的にはいいアイデアだと思うんだが.論文かけるくらいの.


ll は bus に何か情報を垂れ流すんだろうか.そこでエラー? でも,キャッシュ一貫性制御と一緒にやれば,そんなの必要ないと思うんだけど.それとも,まずいこと起こるかなあ.


do{
  if(LL(addr) != expval) return false;
} until( SC(addr, newval) );
return true;

うーん,なるほど.


_KM@鳩研(Fri Jan 28 11:12:23 JST 2005)

 double-checked locking ? > test and test and set

_maeda(Fri Jan 28 12:01:13 JST 2005)
bool lock_test_and_test_and_set(lock_t *lock) {
  return ! lock_in_use(lock)        // Ordinary read operation
    && lock_test_and_set(lock);     // Atomic read-modify-write
}

test-and-set命令はread-modify-writeの間バスを占有します。contentionの頻度が高い場合、まず普通のread操作で、ロックされていないことを確かめてからtest-and-setした方がバスの占有率を下げることができます。

特に、test-and-setをspin-lockとかwait付きspin-lockとかの頻繁なポーリングに用いる際はえらい違いが出てきます。しかし、contentionがほとんどない(test_and_setがほとんど常に成功する)ような場合には、readの分だけわずかに遅くなります。

_ささだ(Fri Jan 28 12:53:17 JST 2005)

 MIPS の load-linked (not load-locked) は読み込んでもバスを占有しない(ように作れる)ので,その他の場合について思いつかず,混乱していただけのようでした.

_ささだ(Fri Jan 28 12:54:28 JST 2005)

 さて,この場合,ll/sc 命令は test-and-set 命令と言うんだろうか.私はそのまま言ってたんだけど.でも,tas命令の定義に「バスを占有すること」とはないから,tas っぽいことをする命令ってことで,いいんかなぁ.

_maeda(Fri Jan 28 16:41:02 JST 2005)

 ll/scやtest-and-setやcompare-and-swapは、まとめてatomic primitiveとか非可分操作命令とか言うんじゃないかな。そのうちtas他いくつかはバスを占有する(せざるをえない)し、ll/scやcasはそうじゃない。tasはあくまでtasであって、ll/scをtasとは決して言わんでしょう。「ll/scをプリミティブとしてtasが実装できる」とかは言うだろうけど。tasなんて今時はやんねーものを非可分操作の代名詞に使うのはどうかと。

_ささだ(Fri Jan 28 17:41:16 JST 2005)

 だって最初にそう教えられたんだもん(拗ねるな).どうもありがとうございました.これではっきりしました.

_maeda(Sat Jan 29 01:26:22 JST 2005)

 すいません。忙しくて気が立っているのか、あちこちでやたら攻撃的になってます…

_ささだ(Sat Jan 29 15:02:58 JST 2005)

 とんでもないです.長年の疑問が解消できたので,いつものごとく感謝です.

_27(Thu)

どこまですればプログラミング言語を極めたことになるのかなぁ.


すみません,「あるプログラミング言語を」と書こうとしていました.


どうやったら 2本も論文かけるんだ orz 一ヶ月で.


#include <stdio.h>
enum{
  A = 1,
  B = 0,
  C,
  D,
};
int main(){
  printf("%d, %d, %d, %d\n", A,B,C,D);
  return 0;
}

enum は値が被ってもいいらしい.-Wall で何も言わない.普通らしい.


class Module
  def enum args, b = nil
    val = 0
    args.split(/,/).each{|c|
      c = c.strip
      break if c.size == 0
      if /(\S+)\s*=\s*(.+)/ =~ c
        const_set($1, val = eval($2, b))
      else
        const_set(c.strip, val+=1)
      end
    }
  end
end

class Hoge
  enum %q{
    X = 10,
    Y = 20,
    Z = 30,
  }
  enum %q{
    A,
    B,
    C,
    D = C + 2,
    E,
    F,
    G,
  }, binding
  
  p [X,Y,Z] #=> [10, 20, 30]
  p [A,B,C,D,E,F,G] #=> [1, 2, 3, 5, 6, 7, 8]
end

前も似たようなの書いたような気がするが.C からのコピペにそれなりに(勿論マクロとかは無理)対応.


C からのコピペで本質的な弱点が.先頭が大文字じゃない定数名には対応できない orz

_かん(Thu Jan 27 17:12:50 JST 2005)

 とりあえず自分で処理系を書ければ一つの極点ではないでしょうか。

_まつもと(Thu Jan 27 17:15:22 JST 2005)

 作ったら、かな。いや、それでも「まだまだ」な気もする。

_shiro(Thu Jan 27 19:14:42 JST 2005)

 処理系を一つ書くだけだと、セマンティクスのダークコーナーが見えなかったりするからなぁ。複数の処理系に馴染んで、言語仕様に対するそれぞれのアプローチやトレードオフを知ることもまた、道の一つではあるような気がする。

_maeda(Thu Jan 27 22:54:18 JST 2005)

値が重複するのは良いんです。

The use of enumerators with = may produce enumeration constants with values that duplicate other values in the same enumeration.

_26(Wed)

SoftWire というものを知る.

使えるだろうか.gcc 専用と割り切って使ってみようか.でも,命令記述をこれでやり直すのは大変そうだなあ.


ちょっと考えたけど,適用はけっこう面倒そうだ.局所最適化には効果があると思うが・・・.Ruby でこいつのラッパー書くのは面白そうだ.


玉.

def perm ary
  ary.size <= 1 ? [ary] :
  begin
    (0...ary.size).inject([]){|ret, i|
      perm(ary[0...i] + ary[i+1..-1]).each{|ep| ret << [ary[i]] + ep}
      ret
    }
  end
end

def solve boxes
  list = perm((0...boxes.size).to_a).map{|prm|
    sum = 0
    boxes.zip(prm){|a, b|
      sum += a[b]
    }
    [sum, prm]
  }.sort_by{|e| -e[0]}
  list.find_all{|e| e[0] == list[0][0]}.map{|e| e[1]}
end

p solve([[5, 10, 5], [20, 10, 5], [10, 20, 10]])
#=> [[2, 0, 1]]

本当にあってるのかなぁ.


今日はコスプレの日だった。


同時通訳スゲエ。でも、さすがに speculation を投機とは訳せてなかった(かなり困ってた)。そりゃそうだよな。

_25(Tue)

なんか信じられないんですけど、通っちゃったらしい。

うーん、review が速すぎる。コメントもなし。なんか罠があるに違いない。


新たなる新発見(頭痛が痛い)。

C のメソッドにも special local ってできるのか orz ('abc'.split(/b/) で落ちる原因)

svar の実装を考え直さないとダメポ

parse.y へのパッチが必要だなぁ。

rb_backref_get() を使わなくして欲しい、と言っても、駄目、ですよね? 多分。

なんとか eval.c だけで・・・。なんともならんか、これは。

parse.y にも yarv が動いてるかどうかのフラグを足さないといけないのか。

ペンディングにしよう。


/abc/ =~ 'abc'
print 'top: '; p $~

1.times{
  print 'blk: ';   p $~
}

pr = proc{
  print 'prc: '; p $~
}
pr.call

def proc2
  /abc/ =~ 'abc'
  proc{
    print 'prc2:'; p $~
  }
end

pr2 = proc2

Thread.new{
  puts 'in other thread:'
  print 'top: '; p $~
  pr.call
  pr2.call
}.join

#=>
top: #<MatchData:0x2ab6028>
blk: #<MatchData:0x2ab6028>
prc: #<MatchData:0x2ab6028>
in other thread:
top: nil
prc: nil
prc2:#<MatchData:0x2ab5ed8>

eval.c を見て、やっと svar の性質がわかった。thread ごとに違うかと思ったら、そんなことはないのね。スレッド切り替え時のカレントスコープでの svar が変わるだけで。うー、やれやれ。

しかし、現在のこの挙動は、ネイティブスレッド化したときに問題になる。どうしたもんだろ。要するに、スレッドごとに別々のストレージを持っているわけではなく、使いまわしているのが問題。

まぁ、Thread.new したときに別々の svar(backref, last_line)であればよかったんだろうけれど。

うまい実装は思いつかない。いっそ、スコープローカルにしてしまうのはどうだろう。

/abc/ =~ 'abc'
1.times{
  p $~   #=> nil
}

駄目かなあ。

案2。本当に thread local。スコープにはストレージ(へのポインタ)はもたず、スレッドに持たせる。

rb_svar(cnt):
  # lfp: frame pointer
  svar_storage = (Thread[lfp] ||= [])
  return &svar_storage[cnt]

でも GC がちょっと面倒。って、駄目だなあ。これだと lfp が一緒で違うフレーム時に共有してしまう。スコープから抜けるごとにこいつを無効化するのは嫌だ。

rb_svar(cnt):
  if lfp[0] == false
    Thread[lfp] = []
    lfp[0] = true
  end
  return &Thread[lfp][cnt]

こうやって、GC を少し頑張ればいいか。少し、じゃないような気もするが。

c method にも lfp[0] を保証してあげないといけないのね。面倒だなぁ。


長すぎる気がするんだけどなあ.


ちょっと規模の大きめな手入れをしているのでコミットが滞ってます。

ガクガクブルブル.



def getproc
  /abc/ =~ 'abc'
  $pr = proc{
    p $~
  }
  proc{
    /123/ =~ '123'
  }
end

pr1 = getproc
pr2 = proc{
  /123/ =~ '123'
  p $~
}


Thread.new{
  $pr.call
  pr1.call
  $pr.call
  p $~
  pr2.call
  p $~
}.join
#=>
#<MatchData:0x2ac66b8>
#<MatchData:0x2ac6550>
nil
#<MatchData:0x2ac64c0>
nil # なんでだろー?

なんとか研究会原稿は書けそうではある.うう,志が低い・・・.

_24(Mon)

自己嫌悪。なんとかしる。


いろいろ教えていただいてありがとうございました。3月くらいに改めて読ませていただきます。


なんとも間抜けな話なんですが、super を実現するにはフレームにそのクラスを積まないといけないんですね。今更気付いた。しかし、どこに積もう orz

他の方法で知る方法はないものか。だって super のためだけに class 積むってなんか嫌。


命令列はその命令列を格納しているクラスを知っているべきか? しかし、C なメソッドの場合、手に負えない。


C な呼び出しのときに klass も積んでおくことにするか。うわー、益々重く。


class C1
  def m
    p :c1
  end
end

class C2 < C1
  def m
    super
    p :c2
  end
end

class C3 < C2
  def m
    super
    p :c3
  end
end

C3.new.m

こんな例を考えていました。現在の YARV はまさに self.class.superclass って実装なのですが、それだと無限ループ(m3 -> m2 -> m2 -> ...)

_maeda(Mon Jan 24 18:43:57 JST 2005)

 よくわからんけどself.class.superclassじゃだめなの?

_shiro(Mon Jan 24 20:53:33 JST 2005)

 「現在実行中のメソッドの実体」を知る方法は無いのかしらん。デバッグとかで使いそうですよね。そしたら、そのメソッドの属するクラスから手繰るとか。CLOSだと多重継承のせいで「あるメソッドの次の優先順位のメソッド」が「あるメソッド」に対して一意に決まらないのであらかじめメソッドチェーンを組み立てないといけないんですが、Rubyなら一意に決まりそうに思えます。

_ささだ(Tue Jan 25 00:27:22 JST 2005)

 まさにそんな感じで直しました(日記中の議論がそんな感じ)。メソッドというデータをどう表すか、整理が必要そうですけど(今はつぎはぎ)。

_maeda(Tue Jan 25 00:40:20 JST 2005)

 親クラス(オブジェクトへのポインタ)をオペランドか定数テーブルに入れちゃえばいいじゃん。

_shiro(Tue Jan 25 06:23:33 JST 2005)

 Rubyってクラス再定義で親が変わるってことはないんでしたっけ>定数テーブル方式

_まつもと(Tue Jan 25 08:15:03 JST 2005)

 スーパークラスが変化することはないんですが、includeでモジュールが付加されることはあります。

_23(Sun)

each が無ければ for を使えばいいじゃない。


X machine (X にプログラミング言語名が入る)を考えたとき、特権モードとユーザモードはどう考えるか。

たとえば、Ruby で考えて、Unix でいうユーザプロセスを考えると、たとえばユーザプロセスに類する Thread を走らせるかもしれない(ユーザスレッドとでも言おうか)。でも、ユーザスレッドからはアクセスして欲しくないものもある(たとえば、共有資源の管理部分など)。さて、どうするんだろう。

  1. Ruby にアクセスレベルを設ける
  2. ユーザスレッドではなく新しい Ruby プロセスを exec する
  3. そんなのシラネー

まぁ、最後のかな。そもそも、「ユーザプロセス」という概念自体があってはいけないのかもしれんけれども。

Lisp machine なんかはどうなってんですかね。

Smalltalk machine とか。

やばいところには触れちゃ不味いような気がするんだけれど、「不味いところを触れる」のがイイ、という意見もあるかもしれず。


セーフレベルを拡張するってのが綺麗かなぁ。でも、例えば ObjectSpace が特権レベルからしかアクセスできないととても不便。

って、アクセスできるところだけアクセスできるように ObjectSpace を拡張すればいいのか。なんだ、簡単じゃん。

さて、アクセスできるところ、ってのを、綺麗に定義できるか?(階層構造を考えなきゃいけないのでちょっと難しい)

って、VM インスタンスを一個作れば、そんなに難しくないか。つまり、「ユーザプロセス」= VM インスタンス。

たいていの OS のモデルで言うと、

  • Kernel = (Special) VM insntace (kernel vm)
    • User Process = VM instance
      • Kernel (User) Thread = Ruby Thread

当たり前すぎて面白くないなぁ。


で、昨日言ってた「1 object に 1 page 以上」の話とつなげる。

1 page はたいてい 4KB なので、ちっちゃいオブジェクト(大半だろう)は大変そうだけど、まぁあんまり考えないことにする。

適当に走らせて、適当に swap out させて、GC は swap out したものを対象にすればいいや。

とか思ったんだけど、swap out してるんだったら sweep するの大変だよな・・・。swap out するときに、それがゴミだったら swap out とか考えたほうがいいんだろうか。アクセスする対象が swap out していたら、勝手に page fault が起こるから、ライトバリアが軽くなる、と考えちゃってよさげか。こんな話、どっかで読んだ気がするんだけど、どこだったっけかな。

page table を kernel vm が管理することで、異なる VM 同士はアクセス制限が可能、な気がする。気だけ。


しかし、Ruby のオブジェクトモデルとは、やっぱり合いそうにないなぁ・・・。

malloc の代わりに、その 4KB の領域に頑張って詰め込もうとするんだろうけど。realloc が頑張れば、なんとかなる?


やっぱり当たり前すぎて面白くないなあ。真面目に考えるなら、きちんとサーベイしないとダメポ。


走らせる機械語のコード(Lisp machine でいうマイクロコードか?)って、誰がどこまで共有できるんかな。 何をどこまで用意しないといけないのかな。


浮動小数点一個につき 1 object = 4KB。ありえねー。immutable object はどっか特別な場所にしまうか。でも、GC されないな。


x86 なら、1 object 1 segment に・・・。すみません、嘘です。


Evaluate MzScheme expressions to your heart's content.

かっこえー。


http://www.cs.utah.edu/flux/oskit/projects.html を見てみると、ML まであるんだねぇ。Java もあるのかー。

その、ML のっていう Express ってのを見ようと思ったら、ページがない。もう飽きちゃったんだろうか。


ちなみに、上記 user process に相当する VM instance もプロセッサ的には特権レベルで動くんだろうなぁ・・・、ってそれじゃページフォールトおこらんではないか。ページ書き換えをユーザレベルで出来るようにすればいいのか。


この日記にもコメント spam が!

_shiro(Sun Jan 23 06:10:48 JST 2005)

Objectと、それが持つデータの格納エリアをくっつけて考えるのは良くないんじゃないか、という気がします。OODBなんかで顕著に出るんですが、Objectのスロットの中には、頻繁に読み出されるけど滅多に変わらないもの、ひたすら更新されるもの、ほとんど触られないけれどデータ自体は巨大なもの、など色々なアクセスパターンがあって、それぞれ異なる格納方式が最適だったりするわけです。一方、プログラム上で見えるObjectは意味的なものでまとめたいわけで。現実にはObjectを分けて委譲したりするんですが、何か本質的でない。

_ささだ(Sun Jan 23 14:28:43 JST 2005)

 一次管理データがページテーブルになるので、なんか工夫できないかなー、とか。

_maeda(Sun Jan 23 16:02:56 JST 2005)

アクセス制御の代表はcapability方式(アクセス主体が「どの資源にアクセスできる」という権限を持つ)とACL方式(アクセスされる資源が、アクセスを許す相手を指定する)ですよね。これらの管理をオブジェクト単位でやろうとした研究はいろいろあります。極端なのは、CPUにチェック機能を持たせたiAPX432ですかね。ページテーブルを利用するやつなら、品川さんの「細粒度保護ドメイン」とか。

_ささだ(Sun Jan 23 17:18:22 JST 2005)

 品川さんの細粒度保護ドメインの話はスレッド単位だったような気がします。既存のOSに無理やりパスを通して軽量化しました、という印象があります。

_ささだ(Sun Jan 23 17:19:35 JST 2005)

 iAPX432 は全然知りませんでした。ぐぐってもよくわからない・・・。

_shelarcy(Sun Jan 23 17:55:06 JST 2005)

古くは Fun OS とか Schemix のような linux のカーネルに付け加えるものよりも、みんな小さい部分だけを書いておく方がお好みみたいですね。(OSkit に頼るとか) 怪しい OS 探しなら http://cliki.tunes.org/Operating%20Systems が使えますね。 SML による Fox とか各種 Lisp OS とか。

_ささだ(Sun Jan 23 19:49:53 JST 2005)

 cliki というのは知らなかったんですが、凄い沢山ありますね。

_昔から(Sun Jan 23 20:20:34 JST 2005)

 ring, capabilityなどなど。MulticsもIBMもiAPX432などなど。やれることはみんなやった。山手線のは焼きなおし。

_び(Sun Jan 23 20:32:56 JST 2005)

 誰でも命令語を書けるからモードを設けざるを得なかった、けれど解釈実行系はただ一つ、しかもシステム管理者しか変更できないなら、特権モードなんていらない、という考え方がある。高水準言語マシンのマイクロコードは変更できた。しかし、Symbolicsもそうだったけど、ブートディスクパックを手でマウントしたおスペなμコードでおスペな自分だけの言語マシンになった。水平型別名VLIWちっくなμコードわかれば何でもできた。応用プログラマなんてμコード知らないし、触る香具師はよほどのもんだったけど、究極のPCだったからそれでもよかった。以って知るべし。

_maeda(Sun Jan 23 22:10:06 JST 2005)

 432についてはEncyclopedia: Intel I432ってのがありました。「細粒度…」はページ単位みたいね。もっと細かい(オブジェクト単位の)アクセス制御の研究もたしかあったよなあ…これか。

_び(Mon Jan 24 10:25:20 JST 2005)

capabilityは、 Rattner, J. and Cox, G.:"Object-Based Computer Arhitecture", SIGARCH, Vol.8, No.6, pp.4-11(1980) これは、iAPX432系列につながる話だったと思う。 そして、かのIBMもIBM System/38(後のAS400)に現れる。 M.E.Houdek, F.G.Soltis, and R. L. Hoffman: "IBM System/38 support for capability-based addressing", In Proceedings of the 8th Symposium on Computer Architecture, pp.341-348, ACM/IEEE (1981) その後に、 M.V.Ramste: "The iAPX432, a Next Generation Microprocessor", Micro-processing and Microprogramming, Vol.11, pp.69-106 (1983) なんてのがある。20年前、時代はまだまだマイクロプログラミングだったし。  アーキテクチャ独立では、ページ単位もやむなし、と思えるが、 とても細粒度とは思えない。かと言ってcapabilityもマイクロアーキテクチャの 衰退とともにすたれちゃったし。

_22(Sat)

未踏ヲチ。

CLIを実装する次世代オペレーティングシステムの開発

MS の CLI だったらしい。さすがにコマンドラインインターフェースは無いか。PM のコメントには

.NETの実行環境であるCLI(Common Language Infrastructre)をハードウェア上に直接実装すると共に ...

とあるので、.NET Chip か! とか思ったが、そうでも無いらしい。要するに、hard <-> CLI <-> OS ということにするんだろう。

私も、YARV マシンというのを作ってみたいとは思っていたんだけれど(2004年度夏のプロシンの、多田先生のを見てね)。

ファイルシステムとかも CLI 上で作るんだろうか。さすがに性能がアレな気がするんだけれど、どうなんだろう。

面白そうだけどね。最適化とか、何か変わったりするんだろうか。メモリ管理とかは「誰」がやるんだろうか。1オブジェクトが1page以上をもつ、とかも面白いかもしれないなあ。64 bit 空間だったら出来るか。あ、これ面白いな。誰か考えてそうだけど。

ウェブデータベースサーバGWSの開発

あれれ、どこかで聞いた記憶が・・・。

情報家電プロトコルスタックの開発

UPnP って、TCP/IP 上のプロトコルだと思ってたんだけど。下は関係ないのかな。しかし、高いな。ふつーに開発するとなると、これくらいかかるってことかな。

確率モデルに基づくCDNを応用した障害耐性サーバーの開発

ディペンダブルコンピューティングはよくわからないのだけれど、どうやってデータのやり取りなどを「セキュア」に行なうんだろう。運用形態とか、よくわからないなあ・・・。これで助かるのってごく一部な気がする。

逆PROXY型Webサーバ拡張システム

mod_rewrite?

共用著作物を利用したコンテンツ作成システムのためのフレームワークの開発

何をするかがわからん orz 「どう開発するか」じゃなくて「何を開発するか」を書くところだと思うぞ、ここは。

人の社会ネットワークを考慮した情報共有のためのアクセス制御環境の開発

これも何を作るのかわからん。お題目だけ並んでる。モノ自体、これから考えます、なのかな。そういうのもありなのかー。

ワークフロー指向の次世代文章アプリケーションプラットフォーム

文書作成方法の1提案?

[[知識・情報共有プラットフォーム SOYA| http://www.ipa.go.jp/jinzai/esp/2004mito2/gaiyou/10-23.html]]

ウェブアプリケーション系の話なのに、なんで「関連Webサイト」がなしなんだろう。

PUI(Paper User Interface)の開発

面白そうなんだけど、紙とのインターフェースがなんだかやっぱりわからん。

三次元GUIスタイルの提案とその開発環境の整備

あー、これいいですね。とりあえず数学知らなくてもできるように・・・は無理かな、やっぱり。

計算機上での活動履歴を利用する記憶の拡張

これも面白いなあ。

京都サーチ縁人 −ローカル・イシュー・ネットワーク

SNS かー。

確率文脈自由文法に基づく遺伝子情報RNAデータベース検索システム

確率文脈自由文法って何?

ところで、伊知地さんのコメントには必ず提案内容のまとめが書いてあって大変わかりやすい。


やっぱり私のテーマはわかりやすいなあ。(私がわかりやすいことしか出来ないから、という話か・・・)


GC は速い。

_shelarcy(Sat Jan 22 15:39:52 JST 2005)

あう。確認してなかった……。前半の部分(context)がないと意味をなさないじゃん。 とりあえずリンク先のアットマークITの記事を見てください。http://www.atmarkit.co.jp/ad/ipa/mitou0408/pj06/framework.html

_ささだ(Sat Jan 22 16:35:40 JST 2005)

 あーなるほど。やっとわかりました>やること

_21(Fri)

英語チェックして〜 と言いに freenode#ruby-lang に行ったら,インライン化について一時間ほど議論してしまった.

私が推す方式は Java っぽい,と言われてしまった.だってさー.

まぁ,見ておけ,きっと何とかして見せるぞー.とりあえず rubydium には当分負けまい.


お前は冠詞が駄目だねえといわれる.映画見るといいよ,というので,何かお勧めがあるかと聞くと,"bad films (they have easy english)" とか言われた.


さて,考えないと・・・.


yarv と検索して「もしかして: year」と言われているうちはまだまだだな(何が).


やはり「バイトコード化」というほうがとおりがいいみたいだなぁ.どうしよ.まぁ,いいかほっとけば.


ちなみに,コンパイルした結果は gcc の場合 direct threaded code になっていて,アドレスのならび(+オペランドいくつか)になってます.

実は bytecode にして,constant pool でどうの,ってしたほうが速かったりするんだろうか.そういう話は論文でも(簡単すぎるのだろう)見たこと無いんだよな.


I-cache とか D-cache とかの略で I$,D$ という表記を見て,悩む.そうか,キャッシュか.

しかし,これは一般的なのか?


IBM Power5 で,同期機構はなんか特別なものがあるかなーと思って調べてみても,ViVA で SMP でバリア同期でゴー,としか書いてない.

とくに,SMT 用の同期命令はないってことなんだろうか(test and set 命令だけなのかな).ご存知の方いらっしゃれば,教えてください.

_20(Thu)

管理職だって馬鹿ばかりじゃないと思うんだけど、なんで変わらないんだろうね? 学生の素朴な疑問。


確率・統計の勉強不足を感じる

耳が痛い痛い痛い.


「Javaたるいなー」とか言っている人の話を聞くと,ECMA Script の人のソースを弄る作業だったらしい.まぁ,一般人にはそんなもんか.

しかし,PHP を CGI と言っているのはどうにかならんかな.いや,CGI で出来ないこともないんだろうけれど.でも,一般人には関係ないか.そんな違い.でもなー,それなりの会社のエンジニアなんだよなぁ.


IRCnet#python-ja で聞いても誰も反応してくれなかったんだけど,free-node#python で聞いたらさくっと教えてくれた.

疑問
Python はネイティブスレッドによる並行実行をサポートしているそうだけれど.リファレンスカウンタ増減に対して排他制御をかけていないようだ.本当にそれでいいの?
答え
Python は一度にひとつのスレッドしかインタプリタを実行しないので平気(global interpreter lock).

ネイティブスレッドを使うのは,遅くなるからやめろ,ともいわれた.

なんか,こんな話は誰でも知ってそうだから,誰も疑問に思わなかったんだろうなあ.何を見れば知れたんだろうか.


まずいまずい.やることやらなければ.


ふと思ったんだけど,wiki の変更 diff をベイジアンフィルタ(でもなんでもいいが)かなんかに食わせて,荒らしかどうか判別するってのはできないんだろうか.

誰かが思いついて,試していそうだけれど.

ま,微妙なの(結論を逆にしてみたり)は無理そうだけど,spam を書き込むとか,そういうのだったら弾くことは可能じゃないだろうか.


【 科 目 名 】情報コミュニケーション工学特別講義4(データマイニング)

という講義があるらしい.いいなぁ.今の学部生.


今日,明日と PRO だったのか.(第 52 回プログラミング研究会 プログラム

 11:30-12:15  スタックベースのML処理系における効率的な一級継続の実装
	皆川宜久,鵜川始陽,八杉昌宏,湯淺太一(京大)

だけ知りたいなあ(ほかは理論系の話ばかりのようで,タイトルを見てもよくわからない orz って,そうでもないな.論文誌を楽しみにしよう).

10:00-10:45  疎な要求駆動型データフロー解析
	滝本宗宏(東京理科大),福岡岳穂(管理工学研),佐々政孝(東工大),
        原田賢一(慶應)

これは面子は coins だけど,どんな話なんだろう.

3月の大岡山は見に行こう.

いつか,発表できるネタは出来るだろうか.


Hash#to_s した文字列で open とかしないからどうでもいいんじゃなかろうか,と思う私はセキュリティ素人.


http://jp.franz.com/base/seminar-2005-02-03.html

**************************************************

(( 2005年02月03日開催 Common Lisp セミナー のご案内 ))

         CLOS MOP (Metaobject Protocol)講義シリーズ 第2回
		Introspection and Analysis (第2章)

**************************************************

CLOS (Common Lisp Object System) の基本的思想は、言語実装のモデルを示した上で、
それを標準化することにより開かれたものにする点にあります。
CLOS 実装における内部構造やインターフェースは MOP(Metaobject Protocol) と呼ばれており、
プログラマや言語設計者がある基準にのっとって操作できるように設計されています。

講義2時間の内、前半1時間は前回講義(第1章)の復習に当て、その後、
第2章「Introspection and Analysis」 を進めるところまで講義します。
CLOS MOP の特徴の1つにリフレクション(自己反映機能)がありますが、
第2章では、ブラウザーやプログラム分析ツールの構築を通して、
CLOS MOP を利用した容易でかつポータブルな、リフレクションプログラミングに挑みます。 

テキストとしては、Gregor Kiczales 著 The Art of the Metaobject Protocol 
(ISBN 0-262-61074-4) を引続き活用します。
できる限り、本文献の第2章「Introspection and Analysis 」を講読の上、当日ご持参下さい。 


< 開催日時/場所 >

2005年02月03日(木) 10:00〜12:00  [受付開始 9:45] 
株式会社数理システム (MSI) セミナールーム [東京]  
新宿駅 南口(東南口)・新南口 徒歩8分   
http://www.msi.co.jp/msi/location.html
(注: セミナールームのロケーションが変わりましたのでご注意ください)

2/3 ですよ.2/3.行きたい.行きたい.でも,2/3.締め切りは 2/7.成果報告会は 2/19.

うーんうーん.

_babie(Fri Jan 21 00:51:40 JST 2005)

確率・統計の勉強不足を感じる

前ここで言ってた「マンガでわかる統計学」面白かったですよ。まっさらの初心者ならオススメ。

「はじめての統計学」鳥居泰彦 著 を今読んでいます。「統計学は腕力が大切だ」という言葉に萌え。

_babie(Fri Jan 21 00:57:04 JST 2005)

 あ、変なんなった。ま、いいか

_ささだ(Fri Jan 21 02:04:25 JST 2005)

 まだマンガ部分しか見てません orz

_たむら(Fri Jan 21 12:51:28 JST 2005)

 判っていてPHPをCGIと呼んでます。大多数の人にはたいした問題じゃないんです。CGIの正しい意味

_19(Wed)

頭の中がダメダメになってしまったので、リハビリのために yarv のリファクタリング。600行ほどのソースを総書き換え。

自分で何を書いたのかさっぱりわからん。あのころの集中力は凄かったらしい。SC あたり。


時々、リテラルを freeze しておきたいときがあるんだけど。配列とか。簡単で汎用的な記法は無いものか。


オブジェクト指向プログラミングテキスト (【オブジェクト倶楽部: 2005-02号】より).


yaccとlex 読んでみたいなあ.岸本さんが持ってたかな,たしか.


HOKKE2005 に行く羽目になってしまった.ホテルとか航空券を探してみた.そんなに高く無さそう.夏の青森よりも全然安そう.

じゃなくて,そもそもネタが全然出来てないよウワーン.締め切りは 2/7.


String#unpack('H*') を教えてもらう.


BK といえば BitKeepr だとおもた.


C++の設計と進化 ほしいなあと思ったけど,ちょっと高い.どうしようかな.


PDPTA 2005 - Feb. 16, 2005: Submission of papers.

いや,書くネタないんだけどさ.

やっぱり,MPS は今年もやるんだろうか.

_しゅどう(Wed Jan 19 16:46:37 JST 2005)

 平鍋さんのC#紹介記事、ウェブに載ってたんですね。知らなかった。

_ひだか(Thu Jan 20 02:08:42 JST 2005)

 「yaccとlexの使い方」 田中 正弘 著 HBJ出版、という本なのですがもしよかったら送りましょうか?

_ささだ(Thu Jan 20 14:15:59 JST 2005)

 あ,そこまでご迷惑をかけるわけには.

_18(Tue)

やってしまった orz


orz

_17(Mon)

喉が痛いのは治ったけど,せきが止まらんなあ.


日本語で10行プログラミング 第1回 執筆=クジラ飛行机 なでしことは? その1

こういう努力が必要なんだろう.きっと.


デブ ミサ サミ,行ってみようかなあ,なんて思ったんですが,登録が面倒そう.どうしようかな.研究会原稿締め切りが多分その頃なんだよなあ.

_15(Sat)

とりあえず遅刻します orz

_14(Fri)

今日の失敗。

int func(int a){
  ...
  {
    int a = 0;
    ...
    func2(..., a); // args の a と勘違い。
  }
}

Index: eval.c
===================================================================
RCS file: /src/ruby/eval.c,v
retrieving revision 1.748
diff -u -r1.748 eval.c
--- eval.c	5 Jan 2005 03:49:50 -0000	1.748
+++ eval.c	14 Jan 2005 02:19:33 -0000
@@ -166,6 +205,47 @@
    4 - no global (non-tainted) variable modification/no direct output
 */
 
+static VALUE profile_recvclass(NODE *node, VALUE klass, ID mid){
+  static VALUE prof = 0;
+  static VALUE prof_mid = 0;
+  static ID prof_id = 0;
+  
+  if(prof == 0){
+    prof     = rb_hash_new();
+    prof_mid = rb_hash_new();
+    rb_define_const(rb_cObject, "ProfileRecvClass", prof);
+    rb_define_const(rb_cObject, "ProfileRecvMID",   prof_mid);
+    prof_id = rb_intern("ProfGO");
+  }
+
+  if(rb_const_defined(rb_cObject, prof_id) == Qfalse ||
+     rb_const_get(rb_cObject, prof_id) == Qfalse){
+    return 0;
+  }
+
+  if(mid < 10){
+    mid = rb_intern("??");
+  }
+  //printf("mid: %08x\n", mid);
+  //printf("sym: %s\n", rb_id2name(mid));
+  
+  {
+    VALUE pos = INT2NUM((VALUE)node);
+    VALUE ary = 0;
+    
+    if((ary = rb_hash_aref(prof, pos)) == Qnil){
+      ary = rb_ary_new();
+      rb_hash_aset(prof, pos, ary);
+      rb_hash_aset(prof_mid, pos, ID2SYM(mid));
+    }
+
+    rb_ary_push(ary, klass);
+    return Qfalse;
+  }
+}
+
+
+
 static VALUE safe_getter _((void));
 static void safe_setter _((VALUE val));
 
@@ -3178,6 +3264,7 @@
 
 	    ruby_current_node = node;
 	    SET_CURRENT_SOURCE();
+          profile_recvclass(node, CLASS_OF(recv), node->nd_mid);
 	    rb_call(CLASS_OF(recv),recv,node->nd_mid,argc,argv,scope);
 	    result = argv[argc-1];
 	}
@@ -3196,6 +3283,7 @@
 
 	    ruby_current_node = node;
 	    SET_CURRENT_SOURCE();
+          profile_recvclass(node, CLASS_OF(recv), node->nd_mid);
 	    result = rb_call(CLASS_OF(recv),recv,node->nd_mid,argc,argv,0);
 	}
 	break;
@@ -3211,12 +3299,14 @@
 
 	    ruby_current_node = node;
 	    SET_CURRENT_SOURCE();
+          profile_recvclass(node, CLASS_OF(self), node->nd_mid);
 	    result = rb_call(CLASS_OF(self),self,node->nd_mid,argc,argv,1);
 	}
 	break;
 
       case NODE_VCALL:
 	SET_CURRENT_SOURCE();
+          profile_recvclass(node, CLASS_OF(self), node->nd_mid);
 	result = rb_call(CLASS_OF(self),self,node->nd_mid,0,0,2);
 	break;
 

こんなのを eval.c にあてて、

$:.unshift '../ruby/lib'
$:.unshift '.ext/i386-mswin32'
p $:
ProfGO = true
#######################
... なんかプログラム
#######################
ProfGO = false



hit = 0
mis = 0
tm  = 0
pm  = []

::ProfileRecvClass.each{|k, v|
  # p v
  prev = false;
  lm = lh = 0
  v.each{|e|
    if prev == e
      lh += 1
    else
      prev = e
      lm += 1
    end
  }
  lm -= 1
  p [::ProfileRecvMID[k], lh, lm]
  hit += lh
  mis += lm
  if lm > 0
    tm  += 1
    pm << [::ProfileRecvMID[k], lh, lm]
  end
}
puts 'total:'
p [hit, mis, tm, ::ProfileRecvMID.size]
p pm.sort_by{|e| e[2]}

こんなプログラムで、インラインメソッドキャッシュのヒット率の統計が取れる。

で、取る対象が思いつかない・・・。


rss/webrick/rexml/cal/soap4r 。xml に偏ってる気がするな。


喉が痛い.

頭もふらふらしてきた.

風邪だ.

でも,まだ原稿は挙がっていない.


とりあえず,出した.


明日は ruby 飲み会です.でも,風邪治ってるかな・・・.もうなんか頭がふらふらする.家に帰るのが大変・・・.でも帰らないと駄目だよな.そもそも明日はセンター入試で入構禁止.

がんばれ受験生.がんばれ俺.

_び(Fri Jan 14 20:35:45 JST 2005)

 調子悪いときは、酒を飲まないで絶対に静養した方がいいと思う。先は長い。人間身体が資本。

_ym(Sat Jan 15 08:31:49 JST 2005)

脈絡ない話で済みません、手元の400行ほどのスクリプトをYARV0.1で動くようにしたんですがその際

"\n".split(/\n/) # SEGV
1.times{|i| 1.times{|j| i }} #SEGV
def f(a); a.each{|e| return e}; end # aが帰ってくる

等に出会いましたので報告します。3つ目は結構びっくりしました。ちなみに実行速度は4秒が3秒になるくらいでした。

_13(Thu)

最終日.


YARV ご紹介してくださった方,どうもありがとうございます.やっぱり,リリースはするもんだな.何もしていないと腐る.モチベーションも.


arton さんが取ってくれたベンチマークを見てみると,C 関数コールのコストが,ppc ではでかいような気がする.sregs の退避・復帰のコストが,やっぱでかいんだろうか.

そうなると,実は AOT compiler は遅いんじゃないだろうか.


無線Lanカードゲット.やった(8人にじゃんけんに勝つ).


研究室に帰室.


  • 夕凪の街 桜の国
  • トキメキ マンガでわかる統計学

Proc#__call__ というのは作ってくれないかなあ.でも,これだと obj() で呼べる,と勘違いしてしまいそうだ(pythonみたいに).ただ単に,send に対する __send__ のつもりだったんだけど.


class << @atdot = 'atdot'
  def net
    self + '.net'
  end
end

def method_missing u, s
  puts "#{u}@#{s}"
end

ko1@atdot.net #=> ko1@atdot.net

valid なプログラムなんだなあ.


ふと,オブジェクト指向スクリプト言語Rubyの p401 を見る.

たぶん別の実装は将来に渡って現れることはないと思いますが。だってこんな面倒なもの作り直す人がいるとは思えませんから :-)

世界はなかなか想像通りにならないようで.

_えむふる(Thu Jan 13 20:53:22 JST 2005)

 全然関係ない話だけど、るびまのインタビューに是非artonさんを

_ささだ(Thu Jan 13 22:11:06 JST 2005)

 どこかで繋がればいいですねえ.

_12(Wed)

えっとね,VMの状態カウントをチェックしたほうが,チェックしないより速くなるの.なんで?


多分最適化の話だと思うんだけど,わけがわからん・・・.


if(cond){
  THEN
}
else{
  ELSE
}

をコンパイルすると,

test cond
je   Label_then
  ELSE
Label_end:

... (大量の命令列)

Label_then
  THEN
  jump Label_end

チェックする場合,

if(cond1 && cond2){
  THEN
}
else{
  ELSE
}
#=>
test cond1
je   Label_then
Label_else:
  ELSE
Label_end:

... (大量の命令列)

Label_then
  test cond2
  jne Label_else
  THEN
  jump Label_end

* こっちのほうが,結構速い

ということでした.icache miss だろうか.分岐予測ミスだろうか.で,大量に投機ミスして,性能低下.

で,なんで後者のほうが速いのか,というと,投機ミスが2回発生しても,2回目の被害は少ないため(多分).

あ,THENサイズ << ELSEサイズ,というところもあるのかな.

__builtin_expect というのを教えてもらったので,解決.

しかし,こんなもんで性能がどさっと変わるのねえ・・・.


まぁ,VM eval 関数がでっけーと,こういう問題もある,ということなのかなあ.


ご無事でなによりで>まつもとさん


人はネガティブなフィードバックはするけど,ポジティブなそれはしない

_11(Tue)

髭そるのは嫌い。


昨日のが遅くなってるのは、GC じゃなくて、ただ単にページングが起こっていただけの予感。


GC.enable/disable を再帰関数中で呼び出した場合、callee が enable にしちゃったら、まずいときがある。

disable
  ...
  disable
  ...
  enable
  ...
enable # 本当はここで enable にしたい

こういうのはどうするべきなんだろうか。

GC.disable/enable にカウンタをもたせればいいんだけれど。そもそも、こんなふうに使うなってことなんだろうか。

flag1 = disable
  ...
  flag2 = disable
  ...
  enable if flag2
  ...
enable if flag1

とするのがいいんだろうか。


まだなんも準備しとらん。何持っていくかなあ。特に、道中の暇つぶし。


やっぱり、たまーの旅では IP unreachable は辛い。でも、そのたまーのために AirH" などを契約するのはもっと辛い。


今更ですが、Gauche:VMの最適化 を発見しますた。

Scheme -> 中間コード -> C を用意して Scheme で書いた Compiler を C にするかあ。やっぱり綺麗だなぁ。

でも、Ruby で Ruby コンパイラはやっぱり嫌だなあ。どうしても性能がね。あと、C のほうが書きやすい(!)と思っているところも。

tail-call とか使うと、また違うんだろうか。あんまり closure が必要とも思えなかったし。

なんと、ローカル変数無しでVMへのポインタから相対アクセスしても、現状より若干速くなるのだ。結構意外。

むむむ・・・。現在、レジスタの構造にべったり依存してるので、この辺を変えるのがかなり大変。

この辺は、もっと切り替えを容易にしておくべきだったなぁ。切り替えられるように変えるべきかも。いや、変えるか。


scheme でコンパイラだと、マクロで綺麗に書けそうだな。


というわけで、湯河原に行ってきます。


予想外の人が居た!


緊張して失敗.ダメダメだ orz 修行が足りん.

前日にプレゼンを作ってちゃ駄目だという例.

・・・.

_maeda(Tue Jan 11 14:24:57 JST 2005)

でも、Ruby で Ruby コンパイラはやっぱり嫌だなあ。どうしても性能がね。

「Rubyは遅い」を自ら認めちゃうわけね:-) 「意外な人」って誰だろう。

_shiro(Tue Jan 11 14:41:15 JST 2005)

 練習したぁ?

_ささだ(Tue Jan 11 15:53:43 JST 2005)

 なんか悔しくなったので,YARV が一段楽したらやり直してみます>Ruby でコンパイラ どちらかというと,すでに C で書いちゃったから書き直すの面倒だなあ,とか. 練習はしてませんでした orz 何回もやってたからいいかな,とか思ったんだけど,構成を変えていたので,あと,なぜかむちゃくちゃ緊張してしまったので,ダメダメでした.いろいろとなめてました.反省.

_10(Mon)

インターネット時代の情報セキュリティと個人情報保護 というのが農工大であるそうです。人が豪華。奥村先生がいらっしゃるということで、聞きに行くか。上原先生も、忙しそうだなあ。

しかし、場所が書いていない。11号館5階だろうか。綺麗なんだけど、エレベータが一機しかないんだよな。


メーリングリストなどで、新年最初の発言に「あけましておめでとうございます」と書くのは、喪中の人がいることを考えるとどうなんだろうか。どうでもいい、が正解なのかも。


無理やり 0.1.0 としてリリースしてみる。


高橋メソッドを使うかどうか迷う.


すげぇ,中田先生マジスゲェ.

いや,今更(凄いことはわかりきってる)だとは思うんですが.ああいう人になりたい.


Matz という言語を作れ,と暗に示してるんだろうか :)


長くても30分しか話せないのにスライドが40枚強.どうしようかな.


1MB強の Ruby Script をコンパイルさせてみた.

if false
  a = 1
  b = 2
  c = 3
  ... 繰り返し 16万行
end

      user     system      total        real
# cygwin
ruby  1.281000   0.031000   1.312000 (  1.318000)
yarv 96.188000   0.094000  96.282000 ( 96.896000)

#mswin32
ruby  1.406000   0.047000   1.453000 (  1.484000)
yarv 93.953000   0.579000  94.532000 ( 94.750000)

やっぱコンパイルはえらい時間かかるな.ちょっと無駄ありすぎか知らん.


if false
def m
  a = 1
  b = 2
  c = 3
end
# 以下 15万行繰り返し
end

# cygwin
ruby  1.546000   0.078000   1.624000 (  1.648000)
yarv  6.718000   0.156000   6.874000 (  6.913000)

# mswin32
ruby  1.688000   0.015000   1.703000 (  1.703000)
yarv  5.703000   0.172000   5.875000 (  5.891000)

うーん,一個でかいのがあるとダメポ.でかい配列一個作るのが大変なんだろうか.GC が多発するからなような気もするけど(GC は起きても回収はされないので,GC の処理だけ損).

コンパイル中は GC 禁止にしようかな.さっくりとメモリが足りなくて落ちたりして.


そういえば,ネイティブスレッド対応したときに GC 禁止ってどういう扱いになるんだろう.


コンパイル中に GC 止めてみた.

# 例1

# cygwin
ruby  1.344000   0.047000   1.391000 (  1.378000)
yarv 95.593000   0.282000  95.875000 ( 96.633000)

# mswin32
ruby  1.454000   0.046000   1.500000 (  1.500000)
yarv 92.500000   0.657000  93.157000 ( 93.359000)

# 例2

# cygwin
ruby  1.594000   0.015000   1.609000 (  1.635000)
yarv  6.563000   0.093000   6.656000 (  6.727000)

# mswin32
ruby  1.594000   0.063000   1.657000 (  1.657000)
yarv  5.360000   0.156000   5.516000 (  5.516000)

うーむ,かわらねえ.なんでじゃー.


まぁ,問題になるのはひとつのスコープが巨大な場合,なので,多分一般的には問題なかろう.

大体コンパイルには4倍時間がかかる,と思っておけばいいかな.まぁ,許容範囲内でしょう.これから最適化などをもっとかけていけば,当然遅くなる可能性はありますが.


髭そらないとなあ.


成人式リリースをしたので,次は何リリースにしようか.


デモ用にマンデルブロ集合を初めて描いてみた。確かに速くなってるんだが、とてもわかりづらい。

そもそも Float を使ってる時点で、やっぱり全然遅い。Fixnum でこれは・・・。

require 'sdl'

Color = White = [255,255,0]
Screen = SDL::setVideoMode(640,480,16,SDL::SWSURFACE)

def rect x, y, w, h
  Screen.fillRect(x, y, w, h, Color)
end

def pt x, y, w, c
  Screen.fillRect(x, y, w, w, [255, c, c])
end

def mandelbrot? x, y, dx
  x = (x-dx*2)/(100.0)
  y = y/(100.0)
  i = 0
  a = 0
  b = 0
  while i<10
    i+=1
    a, b = a*a - b*b + x, 2*a*b + y
    #p [i, a, b]
    return false if a*a + b*b > 2
  end
  true
end


def draw dx
  w = 16
  x = y = 0
  while x<640
    # p x
    while y<480
      pt(x, y, w, dx) if mandelbrot?(x - 200, y - 240, dx)
      y+=w
    end
    x+= w
    y = 0
  end
end


def main
  screen = Screen
  i = 255
  flag = true
  while flag
    while event = SDL::Event2.poll
      if SDL::Event2::Quit === event
        return
      end
    end
    
    screen.fillRect(0,0,640,480,0)
    draw(i)
    screen.updateRect(0,0,0,0)

    # p i
    if i > 0
      i -=1
    else
      return
    end
  end
end

main

なんとなく書いてみて、面白かったのだけど、デモには使えないっぽい。

_み(Mon Jan 10 09:30:13 JST 2005)

 場所は申し込んできた人数で決めるのでは?

_ささだ(Mon Jan 10 10:06:49 JST 2005)

 なるほど。

_shiro(Mon Jan 10 17:37:45 JST 2005)

 どうしてもスライドが削れない場合、(1)性能評価などは、色を使ってぱっと見でわかるようにしといて、後半でぱぱっと見せて流す。質問されたらゆっくり見せればいい (2)話を厳密にしようと思うとどうしても長くなる、というような場合なら、わざと突っ込まれるくらい粗い議論でスライドを流しておき、質問で突っ込まれたら詳細バージョンのスライドを見せて説明する、というような手があります。

_ささだ(Mon Jan 10 18:30:10 JST 2005)

 参考にさせていただきます.

_(う)(Tue Jan 11 11:16:29 JST 2005)

 全ての祝日にリリースしましょう。次は2月11日。

_(う)(Tue Jan 11 11:16:46 JST 2005)

 って、元日にリリースしてないから手遅れ?

_9(Sun)

なぜか変なところでエラーが出る。どうにもそのエラーの原因が掴めない。そもそも、実行するごとに結果が違うし。ウワーン。


ちょっと変えたら動いてしまった。さっぱりわからん。俺が ruby の状態管理を追えてないだけなんだけれど。


まぁ、動いたからいいかぁ?(いい加減)


さて、作業としては、

  • あさってのデモを作る
  • AOT コンパイラ(ruby -> C)を作る

があるんだけど、今日一日で終わるだろうか。コンパイラは正直すぐ出来そうな気はするんだよな。もちろん、厳密なものは無理だけど(例外とか)。


1 bug 去って、また 1 bug


考えてたんだけど、やっぱりVM状態カウンタ(VM状態バージョンっていう言葉はやっぱり変ですね、反省)の比較は、インラインキャッシュでは要らないですねぇ。でも、まぁ現状の実装のほうが楽だから、そのままで行くか。

理由は、キャッシュするための記憶域のセットを管理できるから。多分。でも、その管理、結構面倒くさいな。とくに、ファイナライザ、というか命令列が GC されるとき。

でも、管理はできるので、やっぱりそのようにする必要はあるな。

つまり、先日のはこうなる。

  // 排他制御必要なし
  if(klass == mc->mc_klass){
    mn = mc->mc_method;
  }
  else{
    mn = rb_method_node(klass, id); // メソッド実体を検索
    mc->mc_klass  = klass;
    mc->mc_method = mn;
  }


  // 排他制御必要あり
  if(mc){
    if(klass == mc->mc_klass){
      mn = mc->mc_method;
    }
    else{
      mn = rb_method_node(klass, id);
      if(mc->mc_rewrite_count++ > threshold){
        // これ以上インラインメソッドキャッシュを利用しないようにする
        cancel_inline_method_cache(mc);
        SET_METHOD_CACHE_ENTRY_TO_OPERAND(GET_PC(), 0);
      }
      else{
        cancel_inline_method_cache(mc);
        mc = NEW_METHOD_CACHE_ENTRY();
        register_inline_method_cache(mc);
        mc->mc_klass  = klass;
        mc->mc_method = mn;
        SET_METHOD_CACHE_ENTRY_TO_OPERAND(GET_PC(), mc);
      }
    }
  }
  else{
    // もうメソッドキャッシュはしない
    mn = rb_method_node(klass, id);
  }

なんか、ほんとにこれでいいのかイマイチ疑問だが・・・(この処理の途中で再定義があったとき、正しく動くんだろうか?)

新しく登録するときに、ロックが必要な気がするな。

      else{
        cancel_inline_method_cache(mc);
        mc = register_inline_method_cache(klass, mn);
        SET_METHOD_CACHE_ENTRY_TO_OPERAND(GET_PC(), mc);
      }

こうするのがいいだろうか。

よろしくないなぁ。cancel したエントリを利用する可能性がある。

      else{
        cancel_inline_method_cache(mc);
        register_inline_method_cache(klass, mn, GET_PC());
      }

ここまで必要ですねえ。

で、register_inline_method_cache で、適切なロックが必要。

って、これも2重に cancel して、ゴミが残る可能性があるのか。

      else{
        cancel_and_register_inline_method_cache(mc, klass, mn, GET_PC());
      }

結局、この部分はアトミックに行なわないといけないってことで。


デスクトップでcygwin用に ruby/sdl をビルドして,ノートに持っていったら起動しない.dlopen のエラーと出る.問題はいろいろ思いつくけど,なんでだろう.cygwin が古いだけ,というのもありそう.


発表前なのに,プレゼンの準備をしないで AOT compiler を作り始める.ダメダメだ.

で,リテラルを C に落とすのがとても面倒そうだ.


i=0
while i<100000000
  i+=1
end

#=>
ruby     81.203000   0.031000  81.234000 ( 81.446000)
yarv      6.860000   0.047000   6.907000 (  6.902000)
aotc      0.796000   0.016000   0.812000 (  0.843000)

というわけで,AOT compiler を作ってみた.2時間くらい.

100倍速くなる.まぁ,かなり限定したシチュエーションですが.もちろん,Ruby 全部が 100倍早くなるわけは無い.

でも,目算どおりで,良かった.

メソッド呼び出しを入れると,10倍くらい遅くなる予定.10倍で済むだろうか.

ちなみに,メソッド呼び出しはまだ実装していない.例外なども,なんにも考えてない.

せめて,tak くらいはできるようにしておかないと駄目だろうか.


うーん,きちんと作れば速いものができるような気がするな.とりあえず,aot compiled method を特別に扱う(rb_call0 みたいなところで扱う)ようにして,ごにょごにょと・・・.

NODE_CFUNC みたいなのは,かなり無駄が多かったり多くなかったりするから.

メソッドの起動がどれだけ速かったり遅かったりするかが勝負だなぁ.


ちなみにコンパイラは 100行くらいで ruby でガーーっと書いたので,ダメダメ.グローバル変数とか使ってるし.コミットしたくもないけど,どうしようかなあ.

ちなみに,上記 Ruby プログラムに対して,こんなコードを吐く.

/*
 * == disasm: <ISeq:main@../aotctest.rb>===================================
 * local scope table (size: 5, argc: 0)
 * [ 5] file       [ 4] parsed     [ 3] idx        [ 2] i          
 * 0000 putobject_opopt_INT2FIX_OP_0_CP__SC_xx_ax                        (   2)
 * 0001 setlocal_opopt_2_SC_ax_xx
 * 0002 getlocal_opopt_2_SC_xx_ax                                        (   3)
 * 0003 putobject_SC_ax_ab100000000
 * 0005 opt_lt_SC_ab_ax 
 * 0006 unless_SC_ax_xx 14
 * 0008 getlocal_opopt_2_SC_xx_ax                                        (   4)
 * 0009 putobject_opopt_INT2FIX_OP_1_CP__SC_ax_ab
 * 0010 opt_plus_SC_ab_ax
 * 0011 setlocal_opopt_2_SC_ax_xx
 * 0012 jump_SC_xx_xx   2                                                (   3)
 * 0014 putnil_SC_xx_ax 
 * 0015 end_SC_ax_ax    6
 */

INSN_LABEL_0:
{


INSN_ENTRY(putobject_opopt_INT2FIX_OP_0_CP__SC_xx_ax){
  /* prepare stack status */
{
  /* declare stack push val */
  /* declare and initialize default opes */
#define val INT2FIX(0)
  /* declare and get from iseq */
  /* declare and pop from stack */
  /* for debug */
  DEBUG_ENTER_INSN("putobject_opopt_INT2FIX_OP_0_CP__SC_xx_ax");
  USAGE_ANALYSIS_INSN(BIN(putobject_opopt_INT2FIX_OP_0_CP__SC_xx_ax));
  /* management */
  ADD_PC(1+0);
  #define CURRENT_INSN_putobject_opopt_INT2FIX_OP_0_CP__SC_xx_ax 1
  #define INSN_IS_SC()     1
  #define INSN_LABEL(lab)  LABEL_putobject_opopt_INT2FIX_OP_0_CP__SC_xx_ax_##lab
  #define LABEL_IS_SC(lab) LABEL_##lab##_##f
{
  /* */
  /* push stack val */
  SCREG(a) = val;
#undef val
#undef CURRENT_INSN_putobject_opopt_INT2FIX_OP_0_CP__SC_xx_ax
#undef INSN_IS_SC
#undef INSN_LABEL
#undef LABEL_IS_SC
  END_INSN();
}}
}
}
INSN_LABEL_1:
{


INSN_ENTRY(setlocal_opopt_2_SC_ax_xx){
  /* prepare stack status */
{
  /* declare stack push val */
  /* declare and initialize default opes */
#define idx 2
  /* declare and get from iseq */
  /* declare and pop from stack */
  VALUE val = SCREG(a);
  /* for debug */
  DEBUG_ENTER_INSN("setlocal_opopt_2_SC_ax_xx");
  USAGE_ANALYSIS_INSN(BIN(setlocal_opopt_2_SC_ax_xx));
  /* management */
  ADD_PC(1+0);
  #define CURRENT_INSN_setlocal_opopt_2_SC_ax_xx 1
  #define INSN_IS_SC()     1
  #define INSN_LABEL(lab)  LABEL_setlocal_opopt_2_SC_ax_xx_##lab
  #define LABEL_IS_SC(lab) LABEL_##lab##_##f
{
  (*(GET_LFP() - idx)) = val;
  /* push stack val */
#undef idx
#undef CURRENT_INSN_setlocal_opopt_2_SC_ax_xx
#undef INSN_IS_SC
#undef INSN_LABEL
#undef LABEL_IS_SC
  END_INSN();
}}
}
}
INSN_LABEL_2:
{


INSN_ENTRY(getlocal_opopt_2_SC_xx_ax){
  /* prepare stack status */
{
  /* declare stack push val */
  VALUE val;
  /* declare and initialize default opes */
#define idx 2
  /* declare and get from iseq */
  /* declare and pop from stack */
  /* for debug */
  DEBUG_ENTER_INSN("getlocal_opopt_2_SC_xx_ax");
  USAGE_ANALYSIS_INSN(BIN(getlocal_opopt_2_SC_xx_ax));
  /* management */
  ADD_PC(1+0);
  #define CURRENT_INSN_getlocal_opopt_2_SC_xx_ax 1
  #define INSN_IS_SC()     1
  #define INSN_LABEL(lab)  LABEL_getlocal_opopt_2_SC_xx_ax_##lab
  #define LABEL_IS_SC(lab) LABEL_##lab##_##f
{
  val = *(GET_LFP() - idx);
  /* push stack val */
  SCREG(a) = val;
#undef idx
#undef CURRENT_INSN_getlocal_opopt_2_SC_xx_ax
#undef INSN_IS_SC
#undef INSN_LABEL
#undef LABEL_IS_SC
  END_INSN();
}}
}
}
INSN_LABEL_3:
{
VALUE aot_insn_operands[] = {
INT2FIX(100000000)};

INSN_ENTRY(putobject_SC_ax_ab){
  /* prepare stack status */
{
  /* declare stack push val */
  /* declare and initialize default opes */
  /* declare and get from iseq */
  VALUE val = (VALUE)GET_OPERAND(1);
  /* declare and pop from stack */
  /* for debug */
  DEBUG_ENTER_INSN("putobject_SC_ax_ab");
  USAGE_ANALYSIS_INSN(BIN(putobject_SC_ax_ab));
  /* management */
  ADD_PC(1+1);
  #define CURRENT_INSN_putobject_SC_ax_ab 1
  #define INSN_IS_SC()     1
  #define INSN_LABEL(lab)  LABEL_putobject_SC_ax_ab_##lab
  #define LABEL_IS_SC(lab) LABEL_##lab##_##f
  USAGE_ANALYSIS_OPERAND(BIN(putobject_SC_ax_ab), 0, val);
{
  /* */
  /* push stack val */
  SCREG(b) = val;
#undef CURRENT_INSN_putobject_SC_ax_ab
#undef INSN_IS_SC
#undef INSN_LABEL
#undef LABEL_IS_SC
  END_INSN();
}}
}
}
INSN_LABEL_5:
{


INSN_ENTRY(opt_lt_SC_ab_ax){
  /* prepare stack status */
{
  /* declare stack push val */
  VALUE val;
  /* declare and initialize default opes */
  /* declare and get from iseq */
  /* declare and pop from stack */
  VALUE recv = SCREG(a);
  VALUE obj = SCREG(b);
  /* for debug */
  DEBUG_ENTER_INSN("opt_lt_SC_ab_ax");
  USAGE_ANALYSIS_INSN(BIN(opt_lt_SC_ab_ax));
  /* management */
  ADD_PC(1+0);
  #define CURRENT_INSN_opt_lt_SC_ab_ax 1
  #define INSN_IS_SC()     1
  #define INSN_LABEL(lab)  LABEL_opt_lt_SC_ab_ax_##lab
  #define LABEL_IS_SC(lab) LABEL_##lab##_##f
{
  if(FIXNUM_2_P(recv, obj) && BASIC_OP_UNREDEFINED(FIXNUM_LT)){
    long a = FIX2LONG(recv), b = FIX2LONG(obj);
    
    if (a < b){
      val = Qtrue;
    }
    else{
      val = Qfalse;
    }
  }
  else{
    /* other */
#ifdef YARV_AOT_COMPILED
    rb_funcall(recv, idLT, 1, obj);
#else
    PUSH(recv); PUSH(obj);
    tmp_id = idLT;
    goto LABEL_IS_SC(start_init_in_send_for_opt_1);
#endif
  }
  /* push stack val */
  SCREG(a) = val;
#undef CURRENT_INSN_opt_lt_SC_ab_ax
#undef INSN_IS_SC
#undef INSN_LABEL
#undef LABEL_IS_SC
  END_INSN();
}}
}
}
INSN_LABEL_6:
{
VALUE aot_insn_operands[] = {
0};

INSN_ENTRY(unless_SC_ax_xx){
  /* prepare stack status */
{
  /* declare stack push val */
  /* declare and initialize default opes */
  /* declare and get from iseq */
#define dst INSN_LABEL_14
  /* declare and pop from stack */
  VALUE val = SCREG(a);
  /* for debug */
  DEBUG_ENTER_INSN("unless_SC_ax_xx");
  USAGE_ANALYSIS_INSN(BIN(unless_SC_ax_xx));
  /* management */
  ADD_PC(1+1);
  #define CURRENT_INSN_unless_SC_ax_xx 1
  #define INSN_IS_SC()     1
  #define INSN_LABEL(lab)  LABEL_unless_SC_ax_xx_##lab
  #define LABEL_IS_SC(lab) LABEL_##lab##_##f
  USAGE_ANALYSIS_OPERAND(BIN(unless_SC_ax_xx), 0, dst);
{
  if(!RTEST(val)){
    JUMP(dst);
  }
  /* push stack val */
#undef CURRENT_INSN_unless_SC_ax_xx
#undef INSN_IS_SC
#undef INSN_LABEL
#undef LABEL_IS_SC
  END_INSN();
}}
}
}
INSN_LABEL_8:
{


INSN_ENTRY(getlocal_opopt_2_SC_xx_ax){
  /* prepare stack status */
{
  /* declare stack push val */
  VALUE val;
  /* declare and initialize default opes */
#define idx 2
  /* declare and get from iseq */
  /* declare and pop from stack */
  /* for debug */
  DEBUG_ENTER_INSN("getlocal_opopt_2_SC_xx_ax");
  USAGE_ANALYSIS_INSN(BIN(getlocal_opopt_2_SC_xx_ax));
  /* management */
  ADD_PC(1+0);
  #define CURRENT_INSN_getlocal_opopt_2_SC_xx_ax 1
  #define INSN_IS_SC()     1
  #define INSN_LABEL(lab)  LABEL_getlocal_opopt_2_SC_xx_ax_##lab
  #define LABEL_IS_SC(lab) LABEL_##lab##_##f
{
  val = *(GET_LFP() - idx);
  /* push stack val */
  SCREG(a) = val;
#undef idx
#undef CURRENT_INSN_getlocal_opopt_2_SC_xx_ax
#undef INSN_IS_SC
#undef INSN_LABEL
#undef LABEL_IS_SC
  END_INSN();
}}
}
}
INSN_LABEL_9:
{


INSN_ENTRY(putobject_opopt_INT2FIX_OP_1_CP__SC_ax_ab){
  /* prepare stack status */
{
  /* declare stack push val */
  /* declare and initialize default opes */
#define val INT2FIX(1)
  /* declare and get from iseq */
  /* declare and pop from stack */
  /* for debug */
  DEBUG_ENTER_INSN("putobject_opopt_INT2FIX_OP_1_CP__SC_ax_ab");
  USAGE_ANALYSIS_INSN(BIN(putobject_opopt_INT2FIX_OP_1_CP__SC_ax_ab));
  /* management */
  ADD_PC(1+0);
  #define CURRENT_INSN_putobject_opopt_INT2FIX_OP_1_CP__SC_ax_ab 1
  #define INSN_IS_SC()     1
  #define INSN_LABEL(lab)  LABEL_putobject_opopt_INT2FIX_OP_1_CP__SC_ax_ab_##lab
  #define LABEL_IS_SC(lab) LABEL_##lab##_##f
{
  /* */
  /* push stack val */
  SCREG(b) = val;
#undef val
#undef CURRENT_INSN_putobject_opopt_INT2FIX_OP_1_CP__SC_ax_ab
#undef INSN_IS_SC
#undef INSN_LABEL
#undef LABEL_IS_SC
  END_INSN();
}}
}
}
INSN_LABEL_10:
{


INSN_ENTRY(opt_plus_SC_ab_ax){
  /* prepare stack status */
{
  /* declare stack push val */
  VALUE val;
  /* declare and initialize default opes */
  /* declare and get from iseq */
  /* declare and pop from stack */
  VALUE recv = SCREG(a);
  VALUE obj = SCREG(b);
  /* for debug */
  DEBUG_ENTER_INSN("opt_plus_SC_ab_ax");
  USAGE_ANALYSIS_INSN(BIN(opt_plus_SC_ab_ax));
  /* management */
  ADD_PC(1+0);
  #define CURRENT_INSN_opt_plus_SC_ab_ax 1
  #define INSN_IS_SC()     1
  #define INSN_LABEL(lab)  LABEL_opt_plus_SC_ab_ax_##lab
  #define LABEL_IS_SC(lab) LABEL_##lab##_##f
{
  if(FIXNUM_2_P(recv, obj) && BASIC_OP_UNREDEFINED(FIXNUM_PLUS)){
    /* fixnum + fixnum */
    val = (recv + (obj & (~1)));
    if((~(recv^obj)&(recv^val))&0x80000000){
      val = rb_big_plus(rb_int2big(INT2FIX(recv)),
                        rb_int2big(INT2FIX(obj)));
    }
  }
  else{
#ifdef YARV_AOT_COMPILED
    rb_funcall(recv, idPLUS, 1, obj);
#else
    PUSH(recv); PUSH(obj);
    tmp_id = idPLUS;
    goto LABEL_IS_SC(start_init_in_send_for_opt_1);
#endif
  }
  /* push stack val */
  SCREG(a) = val;
#undef CURRENT_INSN_opt_plus_SC_ab_ax
#undef INSN_IS_SC
#undef INSN_LABEL
#undef LABEL_IS_SC
  END_INSN();
}}
}
}
INSN_LABEL_11:
{


INSN_ENTRY(setlocal_opopt_2_SC_ax_xx){
  /* prepare stack status */
{
  /* declare stack push val */
  /* declare and initialize default opes */
#define idx 2
  /* declare and get from iseq */
  /* declare and pop from stack */
  VALUE val = SCREG(a);
  /* for debug */
  DEBUG_ENTER_INSN("setlocal_opopt_2_SC_ax_xx");
  USAGE_ANALYSIS_INSN(BIN(setlocal_opopt_2_SC_ax_xx));
  /* management */
  ADD_PC(1+0);
  #define CURRENT_INSN_setlocal_opopt_2_SC_ax_xx 1
  #define INSN_IS_SC()     1
  #define INSN_LABEL(lab)  LABEL_setlocal_opopt_2_SC_ax_xx_##lab
  #define LABEL_IS_SC(lab) LABEL_##lab##_##f
{
  (*(GET_LFP() - idx)) = val;
  /* push stack val */
#undef idx
#undef CURRENT_INSN_setlocal_opopt_2_SC_ax_xx
#undef INSN_IS_SC
#undef INSN_LABEL
#undef LABEL_IS_SC
  END_INSN();
}}
}
}
INSN_LABEL_12:
{
VALUE aot_insn_operands[] = {
0};

INSN_ENTRY(jump_SC_xx_xx){
  /* prepare stack status */
{
  /* declare stack push val */
  /* declare and initialize default opes */
  /* declare and get from iseq */
#define dst INSN_LABEL_2
  /* declare and pop from stack */
  /* for debug */
  DEBUG_ENTER_INSN("jump_SC_xx_xx");
  USAGE_ANALYSIS_INSN(BIN(jump_SC_xx_xx));
  /* management */
  ADD_PC(1+1);
  #define CURRENT_INSN_jump_SC_xx_xx 1
  #define INSN_IS_SC()     1
  #define INSN_LABEL(lab)  LABEL_jump_SC_xx_xx_##lab
  #define LABEL_IS_SC(lab) LABEL_##lab##_##f
  USAGE_ANALYSIS_OPERAND(BIN(jump_SC_xx_xx), 0, dst);
{
  JUMP(dst);
  /* push stack val */
#undef CURRENT_INSN_jump_SC_xx_xx
#undef INSN_IS_SC
#undef INSN_LABEL
#undef LABEL_IS_SC
  END_INSN();
}}
}
}
INSN_LABEL_14:
{


INSN_ENTRY(putnil_SC_xx_ax){
  /* prepare stack status */
{
  /* declare stack push val */
  VALUE val;
  /* declare and initialize default opes */
  /* declare and get from iseq */
  /* declare and pop from stack */
  /* for debug */
  DEBUG_ENTER_INSN("putnil_SC_xx_ax");
  USAGE_ANALYSIS_INSN(BIN(putnil_SC_xx_ax));
  /* management */
  ADD_PC(1+0);
  #define CURRENT_INSN_putnil_SC_xx_ax 1
  #define INSN_IS_SC()     1
  #define INSN_LABEL(lab)  LABEL_putnil_SC_xx_ax_##lab
  #define LABEL_IS_SC(lab) LABEL_##lab##_##f
{
  val = Qnil;
  /* push stack val */
  SCREG(a) = val;
#undef CURRENT_INSN_putnil_SC_xx_ax
#undef INSN_IS_SC
#undef INSN_LABEL
#undef LABEL_IS_SC
  END_INSN();
}}
}
}
INSN_LABEL_15:
{
VALUE aot_insn_operands[] = {
INT2FIX(6)};

INSN_ENTRY(end_SC_ax_ax){
  /* prepare stack status */
{
  /* declare stack push val */
  /* declare and initialize default opes */
  /* declare and get from iseq */
  ulong idx = (ulong)GET_OPERAND(1);
  /* declare and pop from stack */
  VALUE val = SCREG(a);
  /* for debug */
  DEBUG_ENTER_INSN("end_SC_ax_ax");
  USAGE_ANALYSIS_INSN(BIN(end_SC_ax_ax));
  /* management */
  ADD_PC(1+1);
  #define CURRENT_INSN_end_SC_ax_ax 1
  #define INSN_IS_SC()     1
  #define INSN_LABEL(lab)  LABEL_end_SC_ax_ax_##lab
  #define LABEL_IS_SC(lab) LABEL_##lab##_##f
  USAGE_ANALYSIS_OPERAND(BIN(end_SC_ax_ax), 0, idx);
{
#ifdef YARV_AOT_COMPILED
  throwed = val;
#else
  struct continuation_frame *cf;
  cf = GET_CONTINUATION_FRAME_PTR(GET_CFP());
  CHECK_FRAME_MAGIC(cf->magic);

//  STACKDUMP();
  
  /* clear environment */
  CLEAR_ENV(GET_DFP());
  SET_SP ((GET_CFP() - idx));
  
  SET_PC (cf->pc );
  SET_LFP(cf->lfp);
  SET_DFP(cf->dfp);
  SET_CFP(cf->cfp);
  
  if(GET_PC() == 0){
    throwed = val;
    goto finish;
  }
#endif
  /* push stack val */
  SCREG(a) = val;
#undef CURRENT_INSN_end_SC_ax_ax
#undef INSN_IS_SC
#undef INSN_LABEL
#undef LABEL_IS_SC
  END_INSN();
}}
}
}

コメントとか自動生成するの,やめたほうがいいかなぁ.

_maeda(Mon Jan 10 01:24:27 JST 2005)

 こめんと… オプションでon/offできるようにすれば?

_ささだ(Mon Jan 10 17:47:43 JST 2005)

 そのようにしてみます.

_8(Sat)

[QUIZ] LCD Numbers (#14)


class Digits
  # 0   1   2   3   4   5   6   7   8   9
  DigitTemplateStr = <<-EOS.split("\n")
   -       -   -       -   -   -   -   - 
  | |   |   |   | | | |   |     | | | | |
           -   -   -   -   -       -   - 
  | |   | |     |   |   | | |   | | |   |
   -       -   -       -   -       -   - 
  EOS

  DigitTemplate = {}
  (0..9).each{|num|
    DigitTemplate[num.to_s] = tmpl = []
    (0..2).each{|x|
      tmpl << xt = []
      (0..4).each{|y|
        xt << DigitTemplateStr[y][x + num * 4 + 2]
      }
    }
  }

  def self.write_digits str, size = 2
    tmpls = []
    len   = str.size
    str.split(//).each{|b|
      raise 'not a digit' unless DigitTemplate[b] 
      tmpls << DigitTemplate[b]
    }
    (0..4).each{|y|
      case y
      when 0, 2, 4
        len.times{|idx|
          (0..2).each{|x|
            print tmpls[idx][x][y].chr * (x == 1 ? size : 1)
          }
          print ' ' * size
        }
        puts
      when 1, 3
        size.times{
          len.times{|idx|
            (0..2).each{|x|
              print tmpls[idx][x][y].chr
              print ' ' * (size - 1) if x == 1
            }
            print ' ' * size
          }
          puts
        }
      end
    }
  end
end

Digits.write_digits '0123456789', 3
__END__
#=>
 ---             ---     ---             ---     ---     ---     ---     ---    
|   |       |       |       |   |   |   |       |           |   |   |   |   |   
|   |       |       |       |   |   |   |       |           |   |   |   |   |   
|   |       |       |       |   |   |   |       |           |   |   |   |   |   
                 ---     ---     ---     ---     ---             ---     ---    
|   |       |   |           |       |       |   |   |       |   |   |       |   
|   |       |   |           |       |       |   |   |       |   |   |       |   
|   |       |   |           |       |       |   |   |       |   |   |       |   
 ---             ---     ---             ---     ---             ---     ---    

ハードコーディングしまくり。


やっとのことで ifunc 動いた orz ...

これで、ずいぶん遅くなってしまったような気がする。やった作業は、主に rb_funcall などでも、必ずスタックフレームを積むようにした。つまり、積む必要の無いものも必ず積む。

これを解決するには、フレーム作成を必要になるまで遅延する、などの対策が考えられる。気が向いたらやろう。


/* test.c */
int func(){
  int a;;
  int b;
  a = b = 1;

  return a+b;
}

このプログラムはエラーになる。何処が間違いが指摘せよ(ただし、C99 より前の C の仕様とする)。

・・・というか、知らなかったよ・・・。VC で確認したんだけど、gcc でも同じだろうか。


プロシンあとちょっとだよー。何もやってない。最近、発表があってもぶつけ本番が多い。発表練習をさぼっている。これは不味い兆候だ。


で、しこしことバックトレースなどに対応しているんだけど、十分ではない。そもそも、現状のバックトレースは正解なのか?


富豪家さんの「移動量」の話に考え込んでしまう。ひきこもりだからなぁ。もっといろんなところにいって、いろんな勉強をしないと。

_shiro(Sat Jan 08 07:58:46 JST 2005)

 うは、これを "LED" と言わないところに時代を感じる。(その前は蛍光管ってのもあったなあ)

_向井(Sat Jan 08 12:47:01 JST 2005)

 gccでも 2.95.x ではダメ。3.xはOK。具体的にいつからOKになったのかまではちょっとわかりません。

_shiro(Sat Jan 08 15:39:50 JST 2005)

 3.xでも-pedanticをつけるとwarningを出してくれます。「C目」になれば2個目の';'は空文に見えます。

_7(Fri)

というわけで、ifunc を実装するための下地を作った。

しかし、Proc オブジェクトがなんできちんと生成できてるのかわからない・・・ orz

もうわけわかんねーよ!

というわけで、メンテナンスは非常に辛そうです・・・。


i=0
while i<10000000
  i+=1
  a = 'abc' # (a) 毎回別オブジェクト生成 (b) 同じオブジェクト
end

#=>
(a) yarv  6.032000   0.000000   6.032000 (  6.105000)
(b) yarv  1.156000   0.000000   1.156000 (  1.169000)

(a) は従来動作。毎回別オブジェクト生成。(b) は同じオブジェクトを使いまわし(多分、freeze されたオブジェクト)。

さて、どうでしょうね。これまでの挙動を変えてまでやる必要があるか。


卜部君案:alias +@ dup

a = +''
a << '...'
a << '...'

ふと、Gauche の cons ってどうやってやってるのか気になったので調べる。というか、Boehm GC。

そういえば、pthread_mutex_trylock があったんだな。あれが十分に高速ならそんなに辛くないか。せいぜい 100 cycle くらいと考えるべきかな。

スレッドローカルなオブジェクトプールを持たせることで速くなるかなーと思ったんだけど。

i386 なら xchg 一個で(たいてい)済むのね。

って、どのディレクティブが有効になってるかよくわかんないな。


python も見てみる。Py_INCREF と Py_DECREF らしいと教わった。・・・なんで排他制御してないの? これ平気なのかなぁ。問題になってないわけはないはずなので、平気なんだろう。カウンタ操作をする場所は同期しているということなのかなあ。

どこかに python のえらい人はいないものだろうか。


今年の流行語は ChangeLog を下さい、なんだろうか。

_6(Thu)

python はどんなふうにメソッドの検索をしているかを知るために、python を触ってみる。


使い方がわからなかった・・・。

インデントでどうのするのはやりづらいと感じたが、慣れると快感になるんだろうか。

__END__ に相当するものがない。どうしよう。かなり困る。


で、ちょいちょい試してみると、LOAD_ATTR という命令でメソッドの探索、というかメソッドオブジェクトを検索しているのがわかる。

で、それを追ってみると、PyObject_GetAttr というので取っているらしい。

で、それを追ってみると、オブジェクトごとに用意(tp_getattr)された attr 取得関数を呼ぶらしい。

で、それを追ってみると、PyObject_GenericGetAttr というのが、その主流らしい。

で、それを追ってみると、なんとなく、毎回基底クラスへ検索をかけているように見える。

・・・なんでこれで python のほうが速いんだろうか。制限が多いからかな。

ソースは見やすいんだけど。

なんでメソッドキャッシュ、というか属性キャッシュとでも言おうか、そういうものを利用しないんだろう。ちょっと入れれば、ずいぶん速くなるような気はするんだが。


文字列リテラルが全部 frozen だったら,どれくらいヤバイんだろう.

a = ''
a << 'hoge'

が,エラー.


余計な文字列オブジェクトを文字列リテラルによって生成しない方法を思いついた気がしたんだけど,気だけだった.

もし破壊的な操作をしようとすると,命令自体を生成命令に変更すればいいや,と思ったんだけど,すでに生成してしまった文字列を考えると,駄目だということに気づく.


'hoge' が frozen で,"hoge" が !frozen で・・・.駄目だろうな.複雑すぎ.

つまり,

a = ''
a << 'hoge'

はエラーで,

a = ""
a << 'hoge'

は成功.


こいつがなんとかなれば,文字列リテラルが沢山あるプログラムは随分高速化されると思うんだがなあ.


Const + 100,なんていう式があったとき,

  getinlinecache label
  getconst  :Const
  putobject 100
  send :+, 1
  setininecache
label:

なんてふうにしてもいいかな,と思ったんだけど,+ が副作用持つように再定義された場合,結果はキャッシュしちゃいけないのね.なんてこった.


やっぱり,変なもん再定義される心配なんてするよりも,再定義が起こったら全部再コンパイルするほうがいいような気がするな.で,定数畳み込みとか全部する.


うわ,トランスメタ、プロセッサ事業から撤退を検討.ショックだ.

_まつもと(Thu Jan 06 01:25:24 JST 2005)

 おそらくベンチマークではメソッド検索がボトルネックになるようなことは少ないからでは。実用プログラムではどうなんでしょうね。

_ささだ(Thu Jan 06 07:21:40 JST 2005)

 あと、関数的に利用するものが多い、というのもあるのかと、ちらっと見て感じました。

_5(Wed)

風の音で目が覚めた。


なんとなくインラインメソッドキャッシュに対応。・・・ごめんなさい、現実逃避です。5% くらい速くなったような気がする。

いや、現実逃避じゃなくて、全国大会原稿のネタなので、今更感はあるんですが・・・。締め切りが 14日。プロシン聞きながら書く予定。

まぁ、速くなってよかった。あとはまとめるだけ。多分、一般的なアプリケーションにおいての「ポリモフィックな」呼び出しの統計を取らないといけないんだろうな。現在の ruby をちょろちょろ変えれば、集計部分だけは作れなくもない気がする。その辺は湯河原で作るか。・


あー、グローバルメソッドキャッシュ自体は、関数呼び出し(しかも、dllをまたぐ)になってるから、フェアじゃないな。インラインにするコスト削減は、結局 EXPR1 の計算コストの削減か。

グローバルな表を見に行くため、キャッシュミスが起こりやすくなるかもしれないというのもあるか。

真面目に評価するには、cache の static 外してなんかするかー。


shiro さんへの返答をこちらで。

えーと、最初は排他制御は「とりあえずいいやー」と考えていました orz

で、考え直したところ、なんとなくよさそうな方法を思いついたので書いておきます。


前提:

Ruby はグローバルメソッドキャッシュをもっています。どれくらいグローバルかというと、VM 一個につき1キャッシュ表をもっています(現状の Ruby は 1 VM == 1 Process)。排他制御は考えていません(現状の Ruby はネイティブメソッド非対応)。

グローバルメソッドキャッシュは、次のようなキャッシュ表です。

expr(class, selector) - cache entry

expr は適当なハッシュ関数です。

c: class, m: selector
#define EXPR1(c,m) ((((c)>>3)^(m))&CACHE_MASK)

ここで、recv.selector(...) とあった場合、class = CLASS_OF(recv) として、expr(class, selector) を計算することで、キャッシュエントリを特定します。cache entry には、正しい class 情報などが入っていますので、それで verify します。もし、正しいエントリなら、エントリにあるメソッド情報を返せば、検索は成功します。

もちろん、これらは排他制御しないとまずいような気がします。多分。でも、前述のとおりしてません(今は必要ないため)。


yarv で実装したインラインキャッシュは、命令のオペランドにメソッド情報をキャッシュしています。

命令にキャッシュエントリを埋め込むことで、高い局所性を期待することができます。

yarv では、send 命令がメソッドディスパッチ命令なので、

send argc, (snip), method_cache

な命令になっています。このメソッドキャッシュは、次のエントリを持つ構造体へのポインタです。

klass
VM state version
メソッド実体

send 命令が実行したとき、まず、klass がキャッシュしたときのクラスか確認します。もし違えば、そのメソッドキャッシュは使えないので順当に検索します。

次に VM の状態バージョンも確認します。もし、メソッドが再定義されていた場合、このバージョンが変わっているため、検知可能です。もし、キャッシュしたときのバージョンとずれていれば、これもやっぱり順当に検索します。

そうでなければ、キャッシュしたメソッドの実体を利用することができます。

具体的には次のようなコードです。

mc: メソッドキャッシュのための情報を格納しているエントリへのポインタ
mn: メソッド実体を格納したい変数

    if(klass == mc->mc_klass &&
       GET_VM_STATE_VERSION() == mc->mc_vmstat){
      mn = mc->mc_method;
    }
    else{
      mn = rb_method_node(klass, id); // メソッド実体を検索
      mc->mc_klass  = klass;
      mc->mc_method = mn;
      mc->mc_vmstat = GET_VM_STATE_VERSION();
    }

これにより、排他制御を考えなければ、インラインキャッシュを実現できました。

もちろん、ポリモルフィックなメソッドには弱いですけど(1エントリキャッシュ)、そういう場合は多分あんまり無いんじゃないか、というのと、もしそうでもグローバルメソッドキャッシュで救うことができます(rb_method_node はグローバルメソッドキャッシュを利用したメソッド検索関数です)。


さて、並行プログラミングを考える場合、たとえば

    if(klass == mc->mc_klass &&
       GET_VM_STATE_VERSION() == mc->mc_vmstat){
      mn = mc->mc_method;
    }

の部分はアトミックでなければなりません。でも、それを実現しようとすると、OS の排他制御機構を利用することに(多分)なり、嬉しくないです。殆どの場合、排他制御が必要な場合なんて起こらないだろうしね。

何がまずいか、と考えると、インラインメソッドキャッシュエントリの一貫性が取れなくなる可能性があるのがまずい、ということになります。

ならば、そのエントリを書き換えるごとに、新しいエントリを作り変えてしまうのはどうでしょうか。

    if(klass == mc->mc_klass &&
       GET_VM_STATE_VERSION() == mc->mc_vmstat){
      mn = mc->mc_method;
    }
    else{
      mn = rb_method_node(klass, id);
      mc = NEW_METHOD_CACHE_ENTRY();
      mc->mc_klass  = klass;
      mc->mc_method = mn;
      mc->mc_vmstat = GET_VM_STATE_VERSION();
      SET_METHOD_CACHE_ENTRY_TO_OPERAND(GET_PC(), mc);
    }

この場合、一貫性は保たれます。キャッシュにヒットする限り、排他制御などの余計なオーバヘッドは要りません。


もちろん、インラインメソッドキャッシュを外したごとに、新しい GC 対象となるオブジェクトを生成するため、メモリ効率は悪くなる可能性はあります。

そのため、しょっちゅうキャッシュミスする場合、それ以上インラインメソッドキャッシュをしない、という選択肢もあります。

  if(mc){
    if(klass == mc->mc_klass &&
       GET_VM_STATE_VERSION() == mc->mc_vmstat){
      mn = mc->mc_method;
    }
    else{
      MC mc = NEW_METHOD_CACHE_ENTRY();
      if(mc->mc_rewrite_count++ > threshold){
        mn = rb_method_node(klass, id);
        // これ以上インラインメソッドキャッシュを利用しないようにする
        SET_METHOD_CACHE_ENTRY_TO_OPERAND(GET_PC(), 0);
      }
      else{
        mn = rb_method_node(klass, id);
        mc = NEW_METHOD_CACHE_ENTRY();
        mc->mc_klass  = klass;
        mc->mc_method = mn;
        mc->mc_vmstat = GET_VM_STATE_VERSION();
        SET_METHOD_CACHE_ENTRY_TO_OPERAND(GET_PC(), mc);
      }
    }
  }
  else{
    // もうメソッドキャッシュはしない
    mn = rb_method_node(klass, id);
  }

自分ではかなりいい線いってるような気がするんですが、どうでしょうか。


もちろん、メソッドキャッシュエントリの作成には排他制御が必要になって、遅いとは思うんだけど。

しかし、全国大会の原稿は2ページなので、ここまでは書けないなぁ。でも PRO に持っていくには弱そうな気がする。

まぁ、いいか。


最初は、メソッドキャッシュエントリは使いまわしできるかと思ったのだけど、再利用可能かどうかって、判定できないんだよね。GC でもしてみないと。

もし再利用できるのならば、そのエントリの集合がわかるわけで、再定義が起こったら、その集合に対して無効処理を行なえて、VM の状態なんか気にする必要なくなるのよね。


淫乱メソッドキャッシュは嫌だ。


あー、キャッシュミスが再定義時にも起こるので、レシーバが安定している場合にも、threshold 越える場合があるのか。なら、場合わけする必要があるのねぇ。


もちろん、この方式は、

  • ポリモフィックなメソッド呼び出しは、そうじゃないものに比べて十分少ない
  • メソッドや定数などの再定義は、十分少ない

ということを仮定しています。


全国大会って、IPSJ 全国大会ね。

FITネタ? とか思ったけど、中大なのかー・・・。東京ならあんまり面白くない。


VM 状態バージョンが変わっただけなら、上書きしても問題ないのだな。多分。

  if(mc){
    if(klass == mc->mc_klass){
      if(GET_VM_STATE_VERSION() == mc->mc_vmstat){
        mn = mc->mc_method;
      }
      else{
        mn = rb_method_node(klass, id);
        mc->mc_method = mn;
        mc->mc_vmstat = GET_VM_STATE_VERSION();
      }
    }
    else{
      mn = rb_method_node(klass, id);
      if(mc->mc_rewrite_count++ > threshold){
        // これ以上インラインメソッドキャッシュを利用しないようにする
        SET_METHOD_CACHE_ENTRY_TO_OPERAND(GET_PC(), 0);
      }
      else{
        mc = NEW_METHOD_CACHE_ENTRY();
        mc->mc_klass  = klass;
        mc->mc_method = mn;
        mc->mc_vmstat = GET_VM_STATE_VERSION();
        SET_METHOD_CACHE_ENTRY_TO_OPERAND(GET_PC(), mc);
      }
    }
  }
  else{
    // もうメソッドキャッシュはしない
    mn = rb_method_node(klass, id);
  }

こんな感じかなぁ。

ちなみに、メソッドキャッシュエントリは NODE。カウンタには nd_file でも使うかね。


さて、これは一般的なな戦略として利用可能か? Ruby に限った話なのか?

それなりに一般性はあるような気はするんだけどなあ。というか、どっかでやってそう。

python とかどうしてるんだろうなあ。

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/65 を見ると、python にはメソッドキャッシュがないっぽいけど、なんせ10年ほど前の話だからなあ。


すみません現実逃避です orz


#include <stdio.h>

class C{
public:
  C(int i){
    i_ = i;
  }
  void pr(){
    printf("%d (%x)\n", i_, this);
  }
} func(int i){
  C c(i);
  printf(">> %d, %x\n", sizeof(c), &c);
  return c;
}

int main(){
  C c1 = func(10);
  C c2 = func(20);
  c1.pr();
  c2.pr();
}

関数の返り値の型でクラスが定義できるとは知らなかった.

と,gcc でやってみたら,怒られた.

d.cpp:13: error: ISO C++ forbids defining types within return type
d.cpp:13: error: semicolon missing after declaration of `class C'
d.cpp: In function `int func(int)':
d.cpp:16: error: cannot convert `C' to `int' in return
_shiro(Wed Jan 05 07:43:09 JST 2005)

 yarvではメソッド