Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rubyで高速なプログラムを書く

 Rubyで高速なプログラムを書く

川崎Ruby会議01 基調講演

Yusuke Endoh

May 20, 2016
Tweet

More Decks by Yusuke Endoh

Other Decks in Programming

Transcript

  1. 自己紹介:遠藤侑介 • Ruby コミッタ(2008年~) – Rubyのテストを増強した – コードカバレッジ測定機能を 実装した –

    キーワード引数を実装した – Ruby 2.0 リリースマネージャ だった – 最近は何もしてない 2 ’06下 ’07上 ’07下 ’08上 60 70 80 90 100 coverage (%) 70% 85% C0カバレッジ遷移
  2. と私 • 立ち上げの時に @chezou さんに相談を受けた • 初期に数回だけ参加した • Kawasaki.rb #005

    (2013-10-23)で発表した – 以上(すみません) • ちなみに Kawasaki.rb #005 で発表したものは 3
  3. eval$s=%q(eval(%w(B=92.chr;N=10.chr;n=0;e=->(s){Q[Q[s,B],?"].gsub(N,B+?n)};E=->(s){'("'+e[s]+'")'};d=->(s,t=?") {s.gsub(t){t+t}};Q=->(s,t=?$){s.gsub(t){B+$&}};puts(eval(%q(%(objectXQRXextendsXApp{H("#{e[%((displayX"#{e[%(Hf X%sX"#{Q[e["TranscriptXshow:X'#{d[%(putsX [regsub X-allX{. }X"#{Q[e[%[intXK(){sJXs=#{E[%(withXAda.Text_Io;pro cedureXqrXisXbeginXAda.Text_Io.P ut_Li ne(" #{d[

    %(BEGINXH("#{d[%(BEGIN{s=#{E[%(forXbXinXSystem.Text .ASCIIEncoding().GetBytes(#{Q[ E[" # include<stdio.h>`nintXK(){puts#{E["#includ e<iostream>`nintXK(){s td::c o ut<<#{E[%(classXProgram{publicXstaticXvoidX Main(){System.Console .Wr ite(#{E[%((defnXf[lXr](if(>(count Xr)45)(lazy-seq(cons ( str"XXXX^""r"^"&")(fXl"")))(let[c( firstXl)](ifXc(f(ne xtXl)(if(=XcX^")(strXrXcXc) (strXrXc)))[(str"X XXX^""r"^".")]))))(doall(map X#(Hln(str"XXXXXX XX"%1))(lazy-cat["IDENT IFICATIONXDIVISI ON.""PROGRAM-ID.XQR.""PR OCEDUREXDIVISI ON."]#{(" console.log"+E[%((wr ite-line"#{Q [%(X:XAX."XXXXXXXXX"X;X:X BXAX."XWRITE(*,*)'"XA X;X:XCXBXTY PEX."X'"XCRX;X:XDXS"XprogramXQR"XC XS^"XHX^"(&"XCXS^"X# {e[%(pack ageXK;import("fmt";"sJs");funcXK(){fmt.Pr int("H^x27"+sJs.Re place("# {e[e[%(importXData.Char`nK=putStrLn$"procedure XK();write(^"DO,1 <-#"++s how(lengthXs)++fXsX1X0;f(x:t)iXc=letXv=foldl(^aXx- >a*2+(modXxX2))0 $takeX 8$iterate(flipXdivX2)$Data.Char.ordXxXin(ifXmodXiX4<1 then"PLEASE"els e"")+ +"DO,1SUB#"++showXi++"<-#"++show(mod(c-v)256)++"^^n"++fX t(i+1)v;f[]_X_ ="PL EASEREADOUT,1^^nPLEA SEGIVEUP^");end";s=#{E[%(.classXpublic XQR`n.superXja va/l ang/Object`n.methodX publicXstaticXK([Ljava/lang/SJ;)V`n.lim itXstackX2`ng ets taticXjava/lang/Syst e m/outXLjava/io/PrintStream;`nldcX"#{e[% (classXQR{pu bli cXstaticXvoidXK(SJ[] v){ SJXc[]=newXSJ[8000],y="",z=y,s="#{z=t= (0..r=q=126) .ma p{ |n|[n,[]]};a=[];%(@s =inte rnalXconstant[#{i=(s=%(PRX"#{Q["H"+E[% (all:`n`t@H fX% sX"#{e[%(.assemblyXt {}.meth odXstaticXvoidXMain(){.entrypointXldst r"#{e["varX u=re quire('u til');u.H('#import<stdio. h>^n');u.H( #{E[% (in tXK(){puts #{E["H_ sJ"+E["Hf"+E[ %(say"# {e["programXQR(output);begi nX#{([*%($_ ="#{s= %(<?phpXecho"#{Q [e["i ntXK(){write#{E ["qr: -write('#{Q[e[%(forXlXin#{E[ e[d[%(eval$ s=%q(#$s) )]]]}.split("^^n") :H( 'cat("sayX^^"'+l+ '^^ "^^n")'))],?']}'),nl,halt."]} ;returnX0;} "]]}"?>);(s+N*( -s.size%6)).byt e s.map{|n|"%07b"%n}. j oin.gsub(/.{6}/){|n|n=n.to_i(2 );((n/26*6+ n+19)%83+46).ch r}}";s|.|$n=ord$ &;substrXunpack(B8,ch r$n-($n<58?-6:$n<91?65:71)),2|e g;s/.{7}/0$ &/g;HXpackXB.le ngth,$_).scan(% r (([X.0-9A-Za-z]+)|( . ))).reverse.map{|a,b|(b) ? "s//chrX#{b .ord}/e":"s//#{ a}/"},"eval"] *"X xX").gsub(/.{1,25 5}/ ){|s|"write( ' # {s}');"}} end."]}"`nend`n) ]]]};returnX 0;}). trXB,?@]}.repla ce(/@ /g,SJ.fr o m CharCode (92)))"]}"callXv oidX[mscor lib]Sys tem.Console:: Write(s J)ret})]}")],/ [ X ^`t;"() {}`[`]]/]}`nBYE)) .size+1}X xXi8]c"#{s.g s u b(/[^` n"]/){B+"%02`x58" %$&.ord}}^00"declareX i32@put s(i8*)defineXi32@K(){star t:%0=cal l X i32@pu ts(i8*Xgetelementp trXinbounds([#{i}XxXi 8]*@s ,i32X0,i32X0))retXi32X0}).bytes{|n | r ,z=z[ n]||(a<<r;q<5624&&z [n]=[q+=1,[]];t[n])}; a<< r;t=[*43..123]-[64,*92..96];a.map{|n | t[n/7 5].chr+t[n%75].chr}* ""}";intXi=0,n=0,q=0, t ;for(;++n<126;)c[n]=""+(char)n;for(; i <s.le ngth();){t=s.charAt(i );q=q*75+t-t/64-t/92 *5-43;if(i++%2>0){y=q<n?c[q]:y;c[n+ + ]=z+ y.charAt(0);System.out .H(z=c[q]);q=0;}}}}) ]}"`ninvokevirtualXjava/io/PrintStr e am/H ln(Ljava/lang/SJ;)V`nre turn`n.endXmethod)+N]})].trXB,?@]}^x27^n","@","^^",- 1))})]} "XDU PXFORXS"X&A,&"XCXNE`x58TX S^"X&A)^",&"XCX0XDOXBX."X&char("XCOUNTX.X."X),&' "XCRXLOOP XS^" X&^"^""XCXS"XendXprogramXQ R"XCXAX."XSTOP"XCRXAX."XEND"XCRXBYEX;XDX), /([^"])/]}"))]).gsu b(/. +/){%((cons"DISPLAY"(f"#{e[$ &]}""")))}}["STOPXRUN."])))).trXB,?~ ]}.Replace("~","^^")); }}) ]};}"]};returnX0;}"]]}):HXjoin( ['+'forXiXinXrange(0,b)],"") +".>").trXB,?!]};gsub(/! /," ^^",s);HXs})]}")END)]}");endXqr;) ]};intXi,j;H( "moduleXQR;initialXbeginX");for( i=0;i<s.length;i++){H("$write(^"XXX") ;for(j=6;j>=0;j --)H((s[i]>>j)%2>0?" ^^t":"X");H("^^n^^t^^nXX^`");");}H("$disp lay(^"^^n^^n^");endXendmodule");returnX0 ;}].reverse],/[`[`]$]/]}"X^x60.&]k),?']}';cr"]]} ")]}")).gsubXB*8,?|]}".replaceAll("^^|","#{B*32 }"))})).gsub(/[HJK^`X]/){[:print,0,:tring,:main,B*2,0,B,?¥s][$&.ord%9]})))*"")#_buffer_for_future_bug_fixes_#_b ################### Quine Relay -- Copyright (c) 2013 Yusuke Endoh (@mametter), @hirekoke ##################)
  4. • とある Ruby プログラム • 実行すると Scala プログラムが出力される • 実行すると

    Scheme プログラムが出力される • … • 実行すると REXX プログラムが出力される • 実行すると元の Ruby プログラムが出力される • ... というプログラム(50 段階の Multi-quine) • (多分)世界初、50言語を使用するプロジェクト • https://github.com/mame/quine-relay/tree/50
  5. の発表の影響 • 不足言語の指摘をもとに、 50から100言語に増強しました • Docker に対応しました • @MakeNowJust さん

    PR ありがとう • GitHubのスター数が 2800 から 5100 に 増えました • GitHub 一千万リポジトリ中 上位 1000 件(0.01%)に入る 6
  6. • Ruby開発チームの最近のスローガン – “Ruby3 will be 3 times faster than

    Ruby2” • 条件[Matz の RubyKaigi 2015 の発表より] – 2020年ごろ – 比較対象は Ruby 2.0 – ベンチマークは開発チームが選ぶ • 小さいが人工的でないもの – 省メモリよりスピード重視 10 チート?
  7. 開発者の悩み • あらゆるプログラムを 3 倍速くするのは困難 – 今の Ruby は(スクリプト言語にしては)すでに相当高速 –

    高速化すべきプログラムが高速になるようにしたい • 『手軽』に試せるベンチマークプログラムがない – Ruby コア開発者 = C 言語マスター ≠ Ruby ヘビーユーザ – Rails のセットアップとか知らない 11
  8. 開発動機 • Ruby3x3を煽るベンチマーク候補 – Ruby 2.0 で 20 fps で動く

     Ruby 3.0 で 60 fps? – CPU律速で現実的なベンチマーク – 最適化ニンジン(Optimization Carrot) • もう1つの動機: Rubyで無理そうなことをやってみたかった – NESの解像度:256 x 240 ピクセル x 60 fps – ループ以外のタスクを 0.8 秒で? (256*240*60).times do |i| ary[0] = 0 end 0.2 秒 15
  9. 開発経緯 • 10年前:実装を試みるも断念 – 当時はRubyもCPUも遅すぎ、NES解析情報もいまいち • 2015/11/08:大江戸 Ruby 会議 04

    – Ruby で NES ROM を作るフレームワーク burn を見る – http://wiki.nesdev.com/ を発見 • 2015/12/11-13:RubyKaigi 2015 – Ruby3x3 を聞き、一通り実装して 3 fps 程度になる • 2015/12/29-31:冬休み前半に 20 fps 達成 • 2016/01/01-03:冬休み後半に 60 fps 達成 • 2016/04/01:公開 16
  10. の関連研究 • Ruby でファミコンプログラミング (takkaw, 2007) – NES ROM でプレゼン

    • Nario:MRI の段階的 GC のデモ (authornari, 2008) – マリオ風ゲームのもたつき現象で リアルアイム性をデモした • Burn (remore, 2014) – Ruby で NES ROM を作れる フレームワーク 17
  11. のアーキテクチャ CPU GPU Program ROM Bitmap ROM Cartridge NES RAM

    (2 kB) VRAM (2 kB) control read read/write read render read/write ※NES 業界ではGPUではなく PPU (Picture Processing Unit) と言うようです interrupt 18 APU
  12. の仕事 • 背景描画(最大のボトルネック、次ページ) • スクロール – VRAMは2画面分あり、1画面分を描画する • スプライト –

    背景にキャラチップを重ねて書ける – 衝突判定:0番スプライトを描画するとき CPU 割り込みする • GPUはNES大勝利の立役者 – 同時期のアーケードと遜色ないグラフィック – 同時期の汎用機(数十万円)より良い(NES:1.5万円) 20
  13. 背景描画 • ピクセルごとに以下を実行 (1秒あたり256 x 240 x 60 = 3.7M回)

    1. そこにあるビットマップ番号をタイル配置データから同定 2. パレット配置データを読んでパレットを同定 3. ビットマップ番号に対応するビットマップデータを読み込む 4. 組み合わせてビデオ信号にして送る タイル配置データ パレット配置データ VRAM GPU 2 1 3 4 描画対象 正確には8ピクセル(1バイト)単位で処理する 21 画像データ Cartridge
  14. の仕事 • 「プログラムROMから命令を読み出して実行する」を 繰り返す • CPU:MOS 6502 – 1.7 MHz

    – レジスタ 8 ビット、アドレス空間 16 ビット – 可変長命令(1 - 3 バイトくらい) – 可変クロック(1 - 9 クロックくらい) – パイプラインなし – 大体秒間 30 万命令くらい処理する • メインメモリ:2 kB – ツイート約 5 つ分(UTF-8 日本語) 23
  15. の仕事 • 以下の波形を合成して出力する – 矩形波 x 2 – 三角波 –

    ノイズ – PCM – 簡単に言えば 3 和音+ドラム • 実装は面倒だが、ボトルネックにはならない – 44100 Hz なら、1 秒間に 44100 x O(1) の処理でよい – GPU は 256x240x60 = 3686400 x O(1) の処理が必要 – ノイズ生成のみ若干重い 24
  16. と の通信 • CPUから見てGPUとAPUはメモリに見える – アドレス0x2000番地に書き込むとGPUへの情報送信 – アドレス0x2000番地から読み込むとGPUからの情報取得 25 アドレス

    内容 0x0000..0x07ff 普通のメモリ 0x2000..0x3fff GPU 0x4000..0x5fff APUとパッド 0x6000..0x7fff 拡張メモリ 0x8000..0xffff プログラムROM char *gpu = (char*)0x2000; *gpu = 0x01;
  17. マッパー • ファミコンカートリッジは単なるソフトウェアではない – 各ゲーム専用の回路が組み込まれている – 発売されたゲームの数だけ回路がある • ROM ファイルには回路の番号だけが書かれている

    – マッパーと呼ばれる • 実用的なエミュレータは、 発売されたゲームの数だけある回路を すべて実装しないといけない – Optcarrot は基本的なマッパーしか対応していない 26
  18. 目標値を設定せよ • 最適化=コードを汚すこと – 目標値を設定しないと際限なくコードを汚くしてしまう • 目標値がないと「小さなことからコツコツと」をやりがち – 大きなことからやれ –

    「わずかでも速くしたい」という煩悩は諸悪の根源 – 本当にとにかく速くしたいなら C 言語へどうぞ • 「XXまで速くすれば◦◦できる、しないとできない」と いう目標がよい – 開発効率・メンテナンス性・マーケティングなどから決める – 達成できるかどうかで価値がまったく変わるのが望ましい • NESの場合 60 fps 30
  19. アルゴリズム最適化を考えよ • Optcarrot の場合 – ナイーブに書いて 3 fps – アルゴリズム・データ構造の改善で

    20 fps (約 7 倍) – メソッド展開とか汚いことやって 80 fps (約 4 倍) • アルゴリズム改善の方が寄与度が高い – アルゴリズムに工夫の余地がなさそうなエミュレータですら – メソッド展開などはわかりやすくて印象に残りやすいが、 通常はメンテナンス性を犠牲にするほどの効果はない 32
  20. 効果を検証せよ • ×「実行してみたら速かった」 – 測定のたびにブレがある – 都合のいい結果を記憶に残して「速くなった」と信じてしまう • 確証バイアス:仮説や信念を検証する際にそれを支持する情報ばか りを集め、反証する情報を無視または集めようとしない傾向のこと

    • 今回:最適化前と後で 30 回ずつ実行時間を計測、 Welch の t 検定で 5% 有意差があることを確認した • 有意差が認められなかったら変更を捨てる  重要 – 頑張って実装した最適化が効果なかったとは認めたくない ものだが、Mottainai の精神は悪 33
  21. • a sampling call-stack profiler for ruby 2.1+ – https://github.com/tmm1/stackprof

    • 特徴 – オーバーヘッドがほとんどない – 測定結果は正確ではない • インタプリタを数ミリ秒おきに監視し、各タイミングでどのメソッドが 実行されているかカウントする(サンプリングプロファイラ) – 使うためにコードを書き換える必要がある • 対抗:ruby-prof – 使いやすい:ruby foo.rb を ruby-prof foo.rb にするだけ – 測定結果が正確:メソッドごとの呼び出し回数 – オーバーヘッドがヤバい 35
  22. の使い方 1. 測りたい部分を以下で囲む – Optcarrot は初期化後のメインループを囲んでいる 2. 実行する  stackprof.dump

    ファイルができる 3. stackprof stackprof.dump で結果を見る – render_pixelが実行時間の20%を占めていることがわかる 36 TOTAL (pct) SAMPLES (pct) FRAME 274 (19.3%) 274 (19.3%) Optcarrot::PPU#render_pixel 160 (11.3%) 160 (11.3%) Optcarrot::PPU#wait_one_clock 106 (7.5%) 105 (7.4%) Optcarrot::CPU#fetch StackProf.run(mode: :cpu, out: "stackprof.dump") { # 測定したいコード }
  23. メソッド単位のプロファイル結果 37 $ stackprof --method "Optcarrot::PPU#render_pixel"¥ stackprof-cpu.dump Optcarrot::PPU#render_pixel (/home/mame/work/optcarrot/lib/optcarrot/pp samples:

    309 self (17.8%) / 309 total (17.8%) callers: 309 ( 100.0%) Optcarrot::PPU#main_loop code: | 803 | def render_pixel | 804 | if @any_show 186 (10.7%) / 186 (10.7%) | 805 | pixel = @bg_enable 17 (1.0%) / 17 (1.0%) | 806 | if @sp_active && ( | 807 | if pixel % 4 == | 808 | pixel = sprite | 809 | else | 810 | @sp_zero_hit = | 811 | pixel = sprite | 812 | end | 813 | end | 814 | else 5 (0.3%) / 5 (0.3%) | 815 | pixel = @scroll_ad
  24. オブジェクト生成の計測 39 $ stackprof obj.dump ================================== Mode: object(1) Samples: 86283

    (0.00% miss rate) GC: 0 (0.00%) ================================== TOTAL (pct) SAMPLES (pct) FRAME 86278 (100.0%) 86278 (100.0%) Optcarrot::PPU#main_loop 86282 (100.0%) 4 (0.0%) Optcarrot::PPU#run 1 (0.0%) 1 (0.0%) Optcarrot::CPU#run 5 (0.0%) 0 (0.0%) Optcarrot::NES#step 5 (0.0%) 0 (0.0%) Optcarrot::NES#run 5 (0.0%) 0 (0.0%) <main> 5 (0.0%) 0 (0.0%) <main> StackProf.run(mode: :object, out: “obj.dump") { # 測定したいコード }
  25. 参考: • Rubyの実装レベルでボトルネックを見るときに使う • 評価器が時間の半分、インスタンス変数読み込みが 16% 、メソッド探索が 6% 占めているとわかる –

    perf については“RubyKaigi 2015 kosaki keynote”で検索 40 51.76% ruby ruby [.] vm_exec_core 16.18% ruby ruby [.] vm_getivar.isra.99 5.89% ruby ruby [.] vm_search_method.isra.79 $ perf record ruby ... $ perf report
  26. アジェンダ • 背景:Ruby3x3 • 事例:Optcarrot • 最適化の心得 • プロファイラの使い方 •

    最適化の具体例 • Ruby 処理系ベンチマーク • 与太話 41
  27. アルゴリズム改善: 実装 • ナイーブな実装:ピクセル単位でエミュレーションする – ハードウェアと同じことをする 画像データ タイル配置データ パレット配置データ VRAM

    GPU 2 1 3 4 この計算をピクセルごとにやるので遅い (一回の計算は大したことないが、一秒間に256 x 240 x 60 回やる) 44 Cartridge
  28. アルゴリズム改善: 実装 • スクリーンを事前描画して、変更部分のみ更新する タイル配置データ パレット配置データ VRAM GPU screen buffer

    VRAMが書き換わった 変更部分のみ更新 フレームごとに ビデオ出力 ※この説明はイメージです 45 画像データ Cartridge
  29. • Method#[] と Array#[] が同名なのを利用したコード – 普通のメモリアクセスは配列参照 2 回でいける –

    GPU・APU制御の場合は Method#[] で対応メソッドが 呼び出される @mem = [] @mem[0x0000] = @main_memory ... @mem[0x2000] = @gpu.method(:read) ... def memory_read(addr) @mem[addr][addr] end 47
  30. その他の最適化 • アルゴリズム最適化でも漸近的計算量だけを見ない – 例:Ruby でリングバッファを実装するより、長さ次第では Array#rotate!を使った方が速いことも • いろいろ事前計算する •

    こんな感じで 20 fps を達成した – Intel® Core™ i7-4500U @ 2.40 GHz / Ubuntu 16.04 0x23C0 | (addr & 0x0C00) | (addr >> 4 & 0x0038) | (addr >> 2 & 0x0007) ARY[addr] 48
  31. • 典型的な実行パスを展開する ™ while catchup? if can_be_fast? # fast-path do_A

    do_B do_C @clock += 3 else case @clock when 1 then do_A when 2 then do_B when 3 then do_C ... end @clock += 1 end end while catchup? case @clock when 1 then do_A when 2 then do_B when 3 then do_C ... end @clock += 1 end 47 fps  63 fps 52
  32. 各 ™ の効果 29.4 40.3 46.6 62.7 68.8 83.2 0.0

    20.0 40.0 60.0 80.0 base method inlining ivar localization fastpath misc CPU misc ProTip™ 1 ProTip™ 2 ProTip™ 3 53
  33. 実装上の工夫 • 手動と言っても本当に手動にはしない – 正規表現を使って自動的に変換する • コードのインデントを頼りにパースする – コードのインデントがちゃんとしている前提 –

    コードチェッカ Rubocop を使って、 コードのインデントが変になっていないことを検証する src = File.read(__FILE__) src.gsub!(/.../) { ... } # method inlining src.gsub!(/.../) { ... } # ivar localization eval(src) 54
  34. アジェンダ • 背景:Ruby3x3 • 事例:Optcarrot • 最適化の心得 • プロファイラの使い方 •

    最適化の具体例 • Ruby3x3 のためのベンチマーク • 与太話 55
  35. の前に、テスト用 の入手方法 • 有志がスクラッチから作ったROMがある – http://www.romhacking.net/ – ベンチマーク用にはこちら(Lan master を使用)

    • もう1つの手段:実際のカートリッジから抽出する – このための回路を通称で「吸出し器」と呼ぶ 56
  36. 自作した吸出し器 ※吸出しプログラムも Ruby で書かれている (arduino_fermata gem を利用) 58 Arduino +

    Firmata 自作シールド (「ホンコン」ベース) カートリッジ 接触悪い パスコンなし
  37. ベンチマーク(アルゴリズム改善) 28.7 28.1 25.5 26.6 25.0 21.4 5.83 21.9 39.2

    25.0 4.10 7.48 27.0 0.0287 0.0 10.0 20.0 30.0 40.0 trunk ruby23 ruby22 ruby21 ruby20 ruby193 ruby187 omrpreview jruby9k jruby17 rubinius mruby topaz opal 59 MRI は改善されている (1.81.92.02.3) OMR は速くない? (MRI 2.2 w/ JIT) JRuby9k が最速 ruby 2.0 で >20 fps (Ruby3x3 的に重要) こんなのでも動く
  38. ベンチマーク(+闇) 28.6 28.0 25.2 26.9 26.1 21.4 5.87 22.8 39.3

    25.3 3.97 7.02 29.3 0.0285 84.0 82.9 78.2 79.6 68.1 64.0 1.46 69.0 2.12 6.13 2.43 0.754 0.0501 0.0 10.0 20.0 30.0 40.0 50.0 60.0 70.0 80.0 90.0 trunk ruby23 ruby22 ruby21 ruby20 ruby193 ruby187 omrpreview jruby9k jruby17 rubinius mruby topaz opal default mode optimized mode 生成したソースコードは JVM 64k バイトコード制限に ひっかかるので JIT できない とのこと 60
  39. 考察 • JRuby 9k が最速: “Deoptimization” が有望 – レアケースを無視して最適化 JIT

    コンパイルしておく – レアケースが発生したら、そこだけ JIT コンパイルしなおす  卜部さんが検討中? https://github.com/shyouhei/ruby/tree/deoptimization_base • OMR はあまり速くない? – JIT は望み薄? • perf によると 50% が評価器だが、これは基本的な組み込みメソッド の処理を含んでいる – OMR はできたばかり • チューニングの余地はあるし、opt_case_dispatch (case 文)の 最適化がまだっぽい 61
  40. 不完全な 実装との戦い • mruby: – require なし、module_function なし、 fiber なし、

    モジュールのインクルードがなんか微妙 • topaz: – String#tr や #% を使ったら異常終了することがある • opal: – パスが動的な require 禁止、binread を自力実装 • 適当な shim を作った – shim 不要なのは MRI と JRuby 9.1.0.0 だけ 62
  41. ベンチマークプログラム • コード生成含めて 5000 行以下 • 非GUIモードならライブラリ一切不要 – miniruby(MRI開発用の不完全なRuby実装)でも動く –

    GUIモードでは ruby-ffi 経由でSDL2 を使う • 基本的な Ruby 機能しか使っていない – ruby 1.8 / mruby / topaz / opal でも shim などで動く • Ruby開発者がRubyの高速化を検証する流れ – miniruby ビルド→ miniruby 実行 63
  42. よく使われるベンチ: • 30000行超 • セットアップ手順 – PostgreSQL の設定 – Redmine

    インストールと初期設定と初期データ登録 – Apache のインストールと設定 – Passenger のインストールと設定 • Ruby開発者がRubyの高速化を検証する流れ – Ruby 全ビルド→インストール→(Apache再起動?)→ ab 実行 64
  43. 『手軽』なベンチマーク • Ruby開発者にとって『手軽』であることが重要 • インストールやセットアップが不要 – git clone だけとコマンド一発で 標準的な設定とデータセットが準備できると理想

    • コマンドラインから ruby foo.rb で起動・終了する – 「このコマンドの実行時間を短縮せよ」だとわかりやすい • 数秒くらいで終わり、毎回の実行時間が安定しているとより良い – rake や rack 経由で実行するのはめんどい • プロファイラやデバッガが使いにくい • Ruby 開発者の Ruby は /usr/bin/ruby にはない • 依存関係が少ないこと、そんなに大きくないこと、など – miniruby で動くと理想(拡張ライブラリなし) 65
  44. は適切なベンチマーク? • 「遅いRubyには向いてないアプリでは?」 – もう Ruby はそこまで遅くない – 「科学技術計算向き」とされる Python

    よりも (コア自体は)速くなってきている • 「にしても、エミュレータはRubyの目指すところか?」 – 個人的にはこういうのも書ける言語になってほしい – 他のものを高速化してほしい人は、自分なりの ベンチマークプログラムを作って公開してください 66
  45. アジェンダ • 背景:Ruby3x3 • 事例:Optcarrot • 最適化の心得 • プロファイラの使い方 •

    最適化の具体例 • Ruby 処理系ベンチマーク • 与太話 67
  46. プログラミング言語は道具? • 「言語は道具。用途に応じて使い分けるべき。」 – 本当? • 「言語は道具」? – 自然言語は思考・意思を伝達する道具というが –

    思考・意思は言語と独立している?本当に? • 「用途に応じて使い分けるべき」? – 言語の習得はコストが高い – 適応力がある人 or ニワカな人 or コードを書かない人 68
  47. つの言語にこだわる つの理由 • 言語が「脳のキャッシュ」に載る – リファレンスを見ずにプログラムできる – ストレスなくプログラミングするために重要 – 多数の言語をこの状態にするのは(自分は)難しい

    • 言語の「土地勘」が身につく – その言語「らしい」書き方がわかる – 「らしい」書き方だと、効率的なプログラムになったり、 バージョンアップ時のハマりを減らせたりする – 言語機能の熟練度が予想できる(akr プロダクトは安心) • 言語の「守備範囲」がよくわかる – その言語でできることは思ったより広い 69
  48. 注意 • 他言語を勉強しなくていいわけではない – 見識は広げるべき – ただ、「広く浅く」だけでは見えてこないものもある • 何が何でも 1

    つの言語で完結させる必要はない – 工夫しまくってファミコンエミュレータが書けたが – 工夫せずに実速が出せる C 言語の方が適切なのは確か – ただ、ちょっとした工夫で言語を変えせずに済む場合も多い • エコシステムの存在はでかい – 科学技術計算やるなら Python に戦ってもしょうがない – 再発明のコストが新言語習得のコストを明らかに上回る 70
  49. まとめ • Rubyで高速プログラムを書く方法を紹介しました – 背景:Ruby3x3 – 事例:Optcarrot – 最適化の心得 –

    プロファイラの使い方 – 最適化の具体例 – Ruby 処理系ベンチマーク – 与太話 71