Slide 1

Slide 1 text

で高速なプログラムを 書く 川崎 会議 基調講演 遠藤侑介 1

Slide 2

Slide 2 text

自己紹介:遠藤侑介 • Ruby コミッタ(2008年~) – Rubyのテストを増強した – コードカバレッジ測定機能を 実装した – キーワード引数を実装した – Ruby 2.0 リリースマネージャ だった – 最近は何もしてない 2 ’06下 ’07上 ’07下 ’08上 60 70 80 90 100 coverage (%) 70% 85% C0カバレッジ遷移

Slide 3

Slide 3 text

と私 • 立ち上げの時に @chezou さんに相談を受けた • 初期に数回だけ参加した • Kawasaki.rb #005 (2013-10-23)で発表した – 以上(すみません) • ちなみに Kawasaki.rb #005 で発表したものは 3

Slide 4

Slide 4 text

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`nintXK(){puts#{E["#includ e`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^n');u.H( #{E[% (in tXK(){puts #{E["H_ sJ"+E["Hf"+E[ %(say"# {e["programXQR(output);begi nX#{([*%($_ ="#{s= %();(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<0){y=q").trXB,?!]};gsub(/! /," ^^",s);HXs})]}")END)]}");endXqr;) ]};intXi,j;H( "moduleXQR;initialXbeginX");for( i=0;i=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 ##################)

Slide 5

Slide 5 text

• とある Ruby プログラム • 実行すると Scala プログラムが出力される • 実行すると Scheme プログラムが出力される • … • 実行すると REXX プログラムが出力される • 実行すると元の Ruby プログラムが出力される • ... というプログラム(50 段階の Multi-quine) • (多分)世界初、50言語を使用するプロジェクト • https://github.com/mame/quine-relay/tree/50

Slide 6

Slide 6 text

の発表の影響 • 不足言語の指摘をもとに、 50から100言語に増強しました • Docker に対応しました • @MakeNowJust さん PR ありがとう • GitHubのスター数が 2800 から 5100 に 増えました • GitHub 一千万リポジトリ中 上位 1000 件(0.01%)に入る 6

Slide 7

Slide 7 text

『超絶技巧プログラミングの世界』 • こんなプログラムや 実装方法の解説が いっぱい載ってます – 技術評論社より発売中 7

Slide 8

Slide 8 text

閑話休題 • 今日の主題:Rubyで高速なプログラムを書く方法 8

Slide 9

Slide 9 text

アジェンダ • 背景:Ruby3x3 • 事例:Optcarrot • 最適化の心得 • プロファイラの使い方 • 最適化の具体例 • Ruby 処理系ベンチマーク • 与太話 9

Slide 10

Slide 10 text

• Ruby開発チームの最近のスローガン – “Ruby3 will be 3 times faster than Ruby2” • 条件[Matz の RubyKaigi 2015 の発表より] – 2020年ごろ – 比較対象は Ruby 2.0 – ベンチマークは開発チームが選ぶ • 小さいが人工的でないもの – 省メモリよりスピード重視 10 チート?

Slide 11

Slide 11 text

開発者の悩み • あらゆるプログラムを 3 倍速くするのは困難 – 今の Ruby は(スクリプト言語にしては)すでに相当高速 – 高速化すべきプログラムが高速になるようにしたい • 『手軽』に試せるベンチマークプログラムがない – Ruby コア開発者 = C 言語マスター ≠ Ruby ヘビーユーザ – Rails のセットアップとか知らない 11

Slide 12

Slide 12 text

手軽なベンチマーク • あなたのプログラムを『手軽』なベンチマークにして 公開しよう! – あなたのプログラムが速くなる(かもしれない) – Ruby 開発に貢献した気になれる(かもしれない) • 『手軽』とは? – 『手軽』で面白くて(一応)実用アプリである ベンチマークプログラム事例として Optcarrot を作りました 12

Slide 13

Slide 13 text

アジェンダ • 背景:Ruby3x3 • 事例:Optcarrot • 最適化の心得 • プロファイラの使い方 • 最適化の具体例 • Ruby 処理系ベンチマーク • 与太話 13

Slide 14

Slide 14 text

とは • Rubyで書かれたNES(ファミコン)エミュレータ Demo 14

Slide 15

Slide 15 text

開発動機 • 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

Slide 16

Slide 16 text

開発経緯 • 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

Slide 17

Slide 17 text

の関連研究 • Ruby でファミコンプログラミング (takkaw, 2007) – NES ROM でプレゼン • Nario:MRI の段階的 GC のデモ (authornari, 2008) – マリオ風ゲームのもたつき現象で リアルアイム性をデモした • Burn (remore, 2014) – Ruby で NES ROM を作れる フレームワーク 17

Slide 18

Slide 18 text

のアーキテクチャ 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

Slide 19

Slide 19 text

エミュレーションのボトルネック GPU 80% CPU 10% その他 10% 実行時間比率 19

Slide 20

Slide 20 text

の仕事 • 背景描画(最大のボトルネック、次ページ) • スクロール – VRAMは2画面分あり、1画面分を描画する • スプライト – 背景にキャラチップを重ねて書ける – 衝突判定:0番スプライトを描画するとき CPU 割り込みする • GPUはNES大勝利の立役者 – 同時期のアーケードと遜色ないグラフィック – 同時期の汎用機(数十万円)より良い(NES:1.5万円) 20

Slide 21

Slide 21 text

背景描画 • ピクセルごとに以下を実行 (1秒あたり256 x 240 x 60 = 3.7M回) 1. そこにあるビットマップ番号をタイル配置データから同定 2. パレット配置データを読んでパレットを同定 3. ビットマップ番号に対応するビットマップデータを読み込む 4. 組み合わせてビデオ信号にして送る タイル配置データ パレット配置データ VRAM GPU 2 1 3 4 描画対象 正確には8ピクセル(1バイト)単位で処理する 21 画像データ Cartridge

Slide 22

Slide 22 text

の仕事はとても大変 http://wiki.nesdev.com/w/index.php/File:Ntsc_timing.png 22

Slide 23

Slide 23 text

の仕事 • 「プログラムROMから命令を読み出して実行する」を 繰り返す • CPU:MOS 6502 – 1.7 MHz – レジスタ 8 ビット、アドレス空間 16 ビット – 可変長命令(1 - 3 バイトくらい) – 可変クロック(1 - 9 クロックくらい) – パイプラインなし – 大体秒間 30 万命令くらい処理する • メインメモリ:2 kB – ツイート約 5 つ分(UTF-8 日本語) 23

Slide 24

Slide 24 text

の仕事 • 以下の波形を合成して出力する – 矩形波 x 2 – 三角波 – ノイズ – PCM – 簡単に言えば 3 和音+ドラム • 実装は面倒だが、ボトルネックにはならない – 44100 Hz なら、1 秒間に 44100 x O(1) の処理でよい – GPU は 256x240x60 = 3686400 x O(1) の処理が必要 – ノイズ生成のみ若干重い 24

Slide 25

Slide 25 text

と の通信 • 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;

Slide 26

Slide 26 text

マッパー • ファミコンカートリッジは単なるソフトウェアではない – 各ゲーム専用の回路が組み込まれている – 発売されたゲームの数だけ回路がある • ROM ファイルには回路の番号だけが書かれている – マッパーと呼ばれる • 実用的なエミュレータは、 発売されたゲームの数だけある回路を すべて実装しないといけない – Optcarrot は基本的なマッパーしか対応していない 26

Slide 27

Slide 27 text

基本的なエミュレーション方法 • GPU・CPU・APU の機能をソフトウェアで実装する – メモリを整数の配列で表す – 「メモリを読み出し、何か計算し、メモリに書き戻す」 を繰り返す 27

Slide 28

Slide 28 text

アジェンダ • 背景:Ruby3x3 • 事例:Optcarrot(NESエミュレータ) • 最適化の心得 • プロファイラの使い方 • 最適化の具体例 • Ruby 処理系ベンチマーク • 与太話 28

Slide 29

Slide 29 text

最適化の心得 1. 目標値を設定せよ 2. ボトルネックをいじれ 3. アルゴリズム最適化を考えよ 4. 効果を検証せよ 29

Slide 30

Slide 30 text

目標値を設定せよ • 最適化=コードを汚すこと – 目標値を設定しないと際限なくコードを汚くしてしまう • 目標値がないと「小さなことからコツコツと」をやりがち – 大きなことからやれ – 「わずかでも速くしたい」という煩悩は諸悪の根源 – 本当にとにかく速くしたいなら C 言語へどうぞ • 「XXまで速くすれば○○できる、しないとできない」と いう目標がよい – 開発効率・メンテナンス性・マーケティングなどから決める – 達成できるかどうかで価値がまったく変わるのが望ましい • NESの場合 60 fps 30

Slide 31

Slide 31 text

ボトルネックをいじれ • 「2 倍高速化」「10%高速化」どっちが良いか? – 答え:不明(それがボトルネックかどうかによる) • 計算時間10%の処理を2倍高速化全体の5%削減 • 計算時間80%の処理を10%高速化全体の8%削減 – ボトルネックの特定にはプロファイラを使う(後述) 31

Slide 32

Slide 32 text

アルゴリズム最適化を考えよ • Optcarrot の場合 – ナイーブに書いて 3 fps – アルゴリズム・データ構造の改善で 20 fps (約 7 倍) – メソッド展開とか汚いことやって 80 fps (約 4 倍) • アルゴリズム改善の方が寄与度が高い – アルゴリズムに工夫の余地がなさそうなエミュレータですら – メソッド展開などはわかりやすくて印象に残りやすいが、 通常はメンテナンス性を犠牲にするほどの効果はない 32

Slide 33

Slide 33 text

効果を検証せよ • ×「実行してみたら速かった」 – 測定のたびにブレがある – 都合のいい結果を記憶に残して「速くなった」と信じてしまう • 確証バイアス:仮説や信念を検証する際にそれを支持する情報ばか りを集め、反証する情報を無視または集めようとしない傾向のこと • 今回:最適化前と後で 30 回ずつ実行時間を計測、 Welch の t 検定で 5% 有意差があることを確認した • 有意差が認められなかったら変更を捨てる  重要 – 頑張って実装した最適化が効果なかったとは認めたくない ものだが、Mottainai の精神は悪 33

Slide 34

Slide 34 text

アジェンダ • 背景:Ruby3x3 • 事例:Optcarrot • 最適化の心得 • プロファイラの使い方 • 最適化の具体例 • Ruby 処理系ベンチマーク • 与太話 34

Slide 35

Slide 35 text

• a sampling call-stack profiler for ruby 2.1+ – https://github.com/tmm1/stackprof • 特徴 – オーバーヘッドがほとんどない – 測定結果は正確ではない • インタプリタを数ミリ秒おきに監視し、各タイミングでどのメソッドが 実行されているかカウントする(サンプリングプロファイラ) – 使うためにコードを書き換える必要がある • 対抗:ruby-prof – 使いやすい:ruby foo.rb を ruby-prof foo.rb にするだけ – 測定結果が正確:メソッドごとの呼び出し回数 – オーバーヘッドがヤバい 35

Slide 36

Slide 36 text

の使い方 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") { # 測定したいコード }

Slide 37

Slide 37 text

メソッド単位のプロファイル結果 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

Slide 38

Slide 38 text

プロファイリングの命は可視化 38 $ stackprof --callgrind stackprof.dump ¥ > stackprof.callgrind $ kcachegrind stackprof.callgrind

Slide 39

Slide 39 text

オブジェクト生成の計測 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%) 5 (0.0%) 0 (0.0%) StackProf.run(mode: :object, out: “obj.dump") { # 測定したいコード }

Slide 40

Slide 40 text

参考: • 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

Slide 41

Slide 41 text

アジェンダ • 背景:Ruby3x3 • 事例:Optcarrot • 最適化の心得 • プロファイラの使い方 • 最適化の具体例 • Ruby 処理系ベンチマーク • 与太話 41

Slide 42

Slide 42 text

アルゴリズム改善: • ナイーブな実装:CPUとGPUの エミュレーションを クロック単位で切り替える – 簡単で正確だが切り替えコストがかかって遅い(3fps) CPU step step step step step step step step step step step step step step step step clock GPU 42 loop do @cpu.step @gpu.step end

Slide 43

Slide 43 text

アルゴリズム改善: • キャッチアップ法: CPUがGPUを制御したら 切り替える – 正確で高速(10fps)(実装は大変だけど頑張る) CPU run catchup run catchup run clock GPU CPU attempts to control GPU 43 loop do clks = @cpu.run @gpu.catchup(clks) end

Slide 44

Slide 44 text

アルゴリズム改善: 実装 • ナイーブな実装:ピクセル単位でエミュレーションする – ハードウェアと同じことをする 画像データ タイル配置データ パレット配置データ VRAM GPU 2 1 3 4 この計算をピクセルごとにやるので遅い (一回の計算は大したことないが、一秒間に256 x 240 x 60 回やる) 44 Cartridge

Slide 45

Slide 45 text

アルゴリズム改善: 実装 • スクリーンを事前描画して、変更部分のみ更新する タイル配置データ パレット配置データ VRAM GPU screen buffer VRAMが書き換わった 変更部分のみ更新 フレームごとに ビデオ出力 ※この説明はイメージです 45 画像データ Cartridge

Slide 46

Slide 46 text

• CPUはGPUやAPUをメモリ越しに制御する • メモリアクセスのナイーブな実装: – 毎回条件判定するのは遅い – メモリマップはカートリッジによって変わるので ハードコーディングはいけてない def memory_read(addr) case addr when 0x0000..0x07ff then @main_memory[addr] when 0x2000..0x3fff then @gpu.read(addr) ... end end 46

Slide 47

Slide 47 text

• 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

Slide 48

Slide 48 text

その他の最適化 • アルゴリズム最適化でも漸近的計算量だけを見ない – 例: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

Slide 49

Slide 49 text

ここから闇 • ここまではプログラムの綺麗さは比較的維持していた • ここからは、プログラムを汚くして高速化する – 普通のRubyプログラマがマネするべき手法ではありません – Rubyインタプリタの高速化の余地を図る研究です 49

Slide 50

Slide 50 text

™ 手動メソッド展開 • メソッド呼び出しは遅いので展開する (当然ボトルネックのみ) while catchup? inc_addr end while catchup? @addr += 1 end 28 fps  40 fps 50

Slide 51

Slide 51 text

™ インスタンス変数のローカル変数化 • インスタンス変数アクセスが遅い – ローカル変数に置き換える while catchup? @addr += 1 end begin addr = @addr while catchup? addr += 1 end ensure @addr = addr end 40 fps  47 fps 51

Slide 52

Slide 52 text

• 典型的な実行パスを展開する ™ 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

Slide 53

Slide 53 text

各 ™ の効果 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

Slide 54

Slide 54 text

実装上の工夫 • 手動と言っても本当に手動にはしない – 正規表現を使って自動的に変換する • コードのインデントを頼りにパースする – コードのインデントがちゃんとしている前提 – コードチェッカ Rubocop を使って、 コードのインデントが変になっていないことを検証する src = File.read(__FILE__) src.gsub!(/.../) { ... } # method inlining src.gsub!(/.../) { ... } # ivar localization eval(src) 54

Slide 55

Slide 55 text

アジェンダ • 背景:Ruby3x3 • 事例:Optcarrot • 最適化の心得 • プロファイラの使い方 • 最適化の具体例 • Ruby3x3 のためのベンチマーク • 与太話 55

Slide 56

Slide 56 text

の前に、テスト用 の入手方法 • 有志がスクラッチから作ったROMがある – http://www.romhacking.net/ – ベンチマーク用にはこちら(Lan master を使用) • もう1つの手段:実際のカートリッジから抽出する – このための回路を通称で「吸出し器」と呼ぶ 56

Slide 57

Slide 57 text

自作した吸出し器 57

Slide 58

Slide 58 text

自作した吸出し器 ※吸出しプログラムも Ruby で書かれている (arduino_fermata gem を利用) 58 Arduino + Firmata 自作シールド (「ホンコン」ベース) カートリッジ 接触悪い パスコンなし

Slide 59

Slide 59 text

ベンチマーク(アルゴリズム改善) 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 的に重要) こんなのでも動く

Slide 60

Slide 60 text

ベンチマーク(+闇) 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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

不完全な 実装との戦い • mruby: – require なし、module_function なし、 fiber なし、 モジュールのインクルードがなんか微妙 • topaz: – String#tr や #% を使ったら異常終了することがある • opal: – パスが動的な require 禁止、binread を自力実装 • 適当な shim を作った – shim 不要なのは MRI と JRuby 9.1.0.0 だけ 62

Slide 63

Slide 63 text

ベンチマークプログラム • コード生成含めて 5000 行以下 • 非GUIモードならライブラリ一切不要 – miniruby(MRI開発用の不完全なRuby実装)でも動く – GUIモードでは ruby-ffi 経由でSDL2 を使う • 基本的な Ruby 機能しか使っていない – ruby 1.8 / mruby / topaz / opal でも shim などで動く • Ruby開発者がRubyの高速化を検証する流れ – miniruby ビルド→ miniruby 実行 63

Slide 64

Slide 64 text

よく使われるベンチ: • 30000行超 • セットアップ手順 – PostgreSQL の設定 – Redmine インストールと初期設定と初期データ登録 – Apache のインストールと設定 – Passenger のインストールと設定 • Ruby開発者がRubyの高速化を検証する流れ – Ruby 全ビルド→インストール→(Apache再起動?)→ ab 実行 64

Slide 65

Slide 65 text

『手軽』なベンチマーク • Ruby開発者にとって『手軽』であることが重要 • インストールやセットアップが不要 – git clone だけとコマンド一発で 標準的な設定とデータセットが準備できると理想 • コマンドラインから ruby foo.rb で起動・終了する – 「このコマンドの実行時間を短縮せよ」だとわかりやすい • 数秒くらいで終わり、毎回の実行時間が安定しているとより良い – rake や rack 経由で実行するのはめんどい • プロファイラやデバッガが使いにくい • Ruby 開発者の Ruby は /usr/bin/ruby にはない • 依存関係が少ないこと、そんなに大きくないこと、など – miniruby で動くと理想(拡張ライブラリなし) 65

Slide 66

Slide 66 text

は適切なベンチマーク? • 「遅いRubyには向いてないアプリでは?」 – もう Ruby はそこまで遅くない – 「科学技術計算向き」とされる Python よりも (コア自体は)速くなってきている • 「にしても、エミュレータはRubyの目指すところか?」 – 個人的にはこういうのも書ける言語になってほしい – 他のものを高速化してほしい人は、自分なりの ベンチマークプログラムを作って公開してください 66

Slide 67

Slide 67 text

アジェンダ • 背景:Ruby3x3 • 事例:Optcarrot • 最適化の心得 • プロファイラの使い方 • 最適化の具体例 • Ruby 処理系ベンチマーク • 与太話 67

Slide 68

Slide 68 text

プログラミング言語は道具? • 「言語は道具。用途に応じて使い分けるべき。」 – 本当? • 「言語は道具」? – 自然言語は思考・意思を伝達する道具というが – 思考・意思は言語と独立している?本当に? • 「用途に応じて使い分けるべき」? – 言語の習得はコストが高い – 適応力がある人 or ニワカな人 or コードを書かない人 68

Slide 69

Slide 69 text

つの言語にこだわる つの理由 • 言語が「脳のキャッシュ」に載る – リファレンスを見ずにプログラムできる – ストレスなくプログラミングするために重要 – 多数の言語をこの状態にするのは(自分は)難しい • 言語の「土地勘」が身につく – その言語「らしい」書き方がわかる – 「らしい」書き方だと、効率的なプログラムになったり、 バージョンアップ時のハマりを減らせたりする – 言語機能の熟練度が予想できる(akr プロダクトは安心) • 言語の「守備範囲」がよくわかる – その言語でできることは思ったより広い 69

Slide 70

Slide 70 text

注意 • 他言語を勉強しなくていいわけではない – 見識は広げるべき – ただ、「広く浅く」だけでは見えてこないものもある • 何が何でも 1 つの言語で完結させる必要はない – 工夫しまくってファミコンエミュレータが書けたが – 工夫せずに実速が出せる C 言語の方が適切なのは確か – ただ、ちょっとした工夫で言語を変えせずに済む場合も多い • エコシステムの存在はでかい – 科学技術計算やるなら Python に戦ってもしょうがない – 再発明のコストが新言語習得のコストを明らかに上回る 70

Slide 71

Slide 71 text

まとめ • Rubyで高速プログラムを書く方法を紹介しました – 背景:Ruby3x3 – 事例:Optcarrot – 最適化の心得 – プロファイラの使い方 – 最適化の具体例 – Ruby 処理系ベンチマーク – 与太話 71