SlideShare a Scribd company logo
1 of 71
Download to read offline
で高速なプログラムを
書く
川崎 会議 基調講演
遠藤侑介
1
自己紹介:遠藤侑介
• Ruby コミッタ(2008年~)
– Rubyのテストを増強した
– コードカバレッジ測定機能を
実装した
– キーワード引数を実装した
– Ruby 2.0 リリースマネージャ
だった
– 最近は何もしてない
2
’06下 ’07上 ’07下 ’08上
60
70
80
90
100
coverage(%)
70%
85%
C0カバレッジ遷移
と私
• 立ち上げの時に @chezou さんに相談を受けた
• 初期に数回だけ参加した
• Kawasaki.rb #005 (2013-10-23)で発表した
– 以上(すみません)
• ちなみに Kawasaki.rb #005 で発表したものは
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 ##################)
• とある Ruby プログラム
• 実行すると Scala プログラムが出力される
• 実行すると Scheme プログラムが出力される
• …
• 実行すると REXX プログラムが出力される
• 実行すると元の Ruby プログラムが出力される
• ... というプログラム(50 段階の Multi-quine)
• (多分)世界初、50言語を使用するプロジェクト
• https://github.com/mame/quine-relay/tree/50
の発表の影響
• 不足言語の指摘をもとに、
50から100言語に増強しました
• Docker に対応しました
• @MakeNowJust さん
PR ありがとう
• GitHubのスター数が
2800 から 5100 に
増えました
• GitHub 一千万リポジトリ中
上位 1000 件(0.01%)に入る
6
『超絶技巧プログラミングの世界』
• こんなプログラムや
実装方法の解説が
いっぱい載ってます
– 技術評論社より発売中
7
閑話休題
• 今日の主題:Rubyで高速なプログラムを書く方法
8
アジェンダ
• 背景:Ruby3x3
• 事例:Optcarrot
• 最適化の心得
• プロファイラの使い方
• 最適化の具体例
• Ruby 処理系ベンチマーク
• 与太話
9
• Ruby開発チームの最近のスローガン
– “Ruby3 will be 3 times faster than Ruby2”
• 条件[Matz の RubyKaigi 2015 の発表より]
– 2020年ごろ
– 比較対象は Ruby 2.0
– ベンチマークは開発チームが選ぶ
• 小さいが人工的でないもの
– 省メモリよりスピード重視
10
チート?
開発者の悩み
• あらゆるプログラムを 3 倍速くするのは困難
– 今の Ruby は(スクリプト言語にしては)すでに相当高速
– 高速化すべきプログラムが高速になるようにしたい
• 『手軽』に試せるベンチマークプログラムがない
– Ruby コア開発者 = C 言語マスター ≠ Ruby ヘビーユーザ
– Rails のセットアップとか知らない
11
手軽なベンチマーク
• あなたのプログラムを『手軽』なベンチマークにして
公開しよう!
– あなたのプログラムが速くなる(かもしれない)
– Ruby 開発に貢献した気になれる(かもしれない)
• 『手軽』とは?
– 『手軽』で面白くて(一応)実用アプリである
ベンチマークプログラム事例として Optcarrot を作りました
12
アジェンダ
• 背景:Ruby3x3
• 事例:Optcarrot
• 最適化の心得
• プロファイラの使い方
• 最適化の具体例
• Ruby 処理系ベンチマーク
• 与太話
13
とは
• Rubyで書かれたNES(ファミコン)エミュレータ
Demo
14
開発動機
• 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
開発経緯
• 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
の関連研究
• Ruby でファミコンプログラミング
(takkaw, 2007)
– NES ROM でプレゼン
• Nario:MRI の段階的 GC のデモ
(authornari, 2008)
– マリオ風ゲームのもたつき現象で
リアルアイム性をデモした
• Burn (remore, 2014)
– Ruby で NES ROM を作れる
フレームワーク
17
のアーキテクチャ
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
エミュレーションのボトルネック
GPU
80%
CPU
10%
その他
10%
実行時間比率
19
の仕事
• 背景描画(最大のボトルネック、次ページ)
• スクロール
– VRAMは2画面分あり、1画面分を描画する
• スプライト
– 背景にキャラチップを重ねて書ける
– 衝突判定:0番スプライトを描画するとき CPU 割り込みする
• GPUはNES大勝利の立役者
– 同時期のアーケードと遜色ないグラフィック
– 同時期の汎用機(数十万円)より良い(NES:1.5万円)
20
背景描画
• ピクセルごとに以下を実行 (1秒あたり256 x 240 x 60 = 3.7M回)
1. そこにあるビットマップ番号をタイル配置データから同定
2. パレット配置データを読んでパレットを同定
3. ビットマップ番号に対応するビットマップデータを読み込む
4. 組み合わせてビデオ信号にして送る
タイル配置データ
パレット配置データ
VRAM
GPU2
1
3
4
描画対象
正確には8ピクセル(1バイト)単位で処理する
21
画像データ
Cartridge
の仕事はとても大変
http://wiki.nesdev.com/w/index.php/File:Ntsc_timing.png
22
の仕事
• 「プログラムROMから命令を読み出して実行する」を
繰り返す
• CPU:MOS 6502
– 1.7 MHz
– レジスタ 8 ビット、アドレス空間 16 ビット
– 可変長命令(1 - 3 バイトくらい)
– 可変クロック(1 - 9 クロックくらい)
– パイプラインなし
– 大体秒間 30 万命令くらい処理する
• メインメモリ:2 kB
– ツイート約 5 つ分(UTF-8 日本語)
23
の仕事
• 以下の波形を合成して出力する
– 矩形波 x 2
– 三角波
– ノイズ
– PCM
– 簡単に言えば 3 和音+ドラム
• 実装は面倒だが、ボトルネックにはならない
– 44100 Hz なら、1 秒間に 44100 x O(1) の処理でよい
– GPU は 256x240x60 = 3686400 x O(1) の処理が必要
– ノイズ生成のみ若干重い
24
と の通信
• 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;
マッパー
• ファミコンカートリッジは単なるソフトウェアではない
– 各ゲーム専用の回路が組み込まれている
– 発売されたゲームの数だけ回路がある
• ROM ファイルには回路の番号だけが書かれている
– マッパーと呼ばれる
• 実用的なエミュレータは、
発売されたゲームの数だけある回路を
すべて実装しないといけない
– Optcarrot は基本的なマッパーしか対応していない
26
基本的なエミュレーション方法
• GPU・CPU・APU の機能をソフトウェアで実装する
– メモリを整数の配列で表す
– 「メモリを読み出し、何か計算し、メモリに書き戻す」
を繰り返す
27
アジェンダ
• 背景:Ruby3x3
• 事例:Optcarrot(NESエミュレータ)
• 最適化の心得
• プロファイラの使い方
• 最適化の具体例
• Ruby 処理系ベンチマーク
• 与太話
28
最適化の心得
1. 目標値を設定せよ
2. ボトルネックをいじれ
3. アルゴリズム最適化を考えよ
4. 効果を検証せよ
29
目標値を設定せよ
• 最適化=コードを汚すこと
– 目標値を設定しないと際限なくコードを汚くしてしまう
• 目標値がないと「小さなことからコツコツと」をやりがち
– 大きなことからやれ
– 「わずかでも速くしたい」という煩悩は諸悪の根源
– 本当にとにかく速くしたいなら C 言語へどうぞ
• 「XXまで速くすれば○○できる、しないとできない」と
いう目標がよい
– 開発効率・メンテナンス性・マーケティングなどから決める
– 達成できるかどうかで価値がまったく変わるのが望ましい
• NESの場合 60 fps
30
ボトルネックをいじれ
• 「2 倍高速化」「10%高速化」どっちが良いか?
– 答え:不明(それがボトルネックかどうかによる)
• 計算時間10%の処理を2倍高速化全体の5%削減
• 計算時間80%の処理を10%高速化全体の8%削減
– ボトルネックの特定にはプロファイラを使う(後述)
31
アルゴリズム最適化を考えよ
• Optcarrot の場合
– ナイーブに書いて 3 fps
– アルゴリズム・データ構造の改善で 20 fps (約 7 倍)
– メソッド展開とか汚いことやって 80 fps (約 4 倍)
• アルゴリズム改善の方が寄与度が高い
– アルゴリズムに工夫の余地がなさそうなエミュレータですら
– メソッド展開などはわかりやすくて印象に残りやすいが、
通常はメンテナンス性を犠牲にするほどの効果はない
32
効果を検証せよ
• ×「実行してみたら速かった」
– 測定のたびにブレがある
– 都合のいい結果を記憶に残して「速くなった」と信じてしまう
• 確証バイアス:仮説や信念を検証する際にそれを支持する情報ばか
りを集め、反証する情報を無視または集めようとしない傾向のこと
• 今回:最適化前と後で 30 回ずつ実行時間を計測、
Welch の t 検定で 5% 有意差があることを確認した
• 有意差が認められなかったら変更を捨てる  重要
– 頑張って実装した最適化が効果なかったとは認めたくない
ものだが、Mottainai の精神は悪
33
アジェンダ
• 背景:Ruby3x3
• 事例:Optcarrot
• 最適化の心得
• プロファイラの使い方
• 最適化の具体例
• Ruby 処理系ベンチマーク
• 与太話
34
• a sampling call-stack profiler for ruby 2.1+
– https://github.com/tmm1/stackprof
• 特徴
– オーバーヘッドがほとんどない
– 測定結果は正確ではない
• インタプリタを数ミリ秒おきに監視し、各タイミングでどのメソッドが
実行されているかカウントする(サンプリングプロファイラ)
– 使うためにコードを書き換える必要がある
• 対抗:ruby-prof
– 使いやすい:ruby foo.rb を ruby-prof foo.rb にするだけ
– 測定結果が正確:メソッドごとの呼び出し回数
– オーバーヘッドがヤバい
35
の使い方
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") {
# 測定したいコード
}
メソッド単位のプロファイル結果
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
プロファイリングの命は可視化
38
$ stackprof --callgrind stackprof.dump ¥
> stackprof.callgrind
$ kcachegrind stackprof.callgrind
オブジェクト生成の計測
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") {
# 測定したいコード
}
参考:
• 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
アジェンダ
• 背景:Ruby3x3
• 事例:Optcarrot
• 最適化の心得
• プロファイラの使い方
• 最適化の具体例
• Ruby 処理系ベンチマーク
• 与太話
41
アルゴリズム改善:
• ナイーブな実装: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
アルゴリズム改善:
• キャッチアップ法:
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
アルゴリズム改善: 実装
• ナイーブな実装:ピクセル単位でエミュレーションする
– ハードウェアと同じことをする
画像データ
タイル配置データ
パレット配置データ
VRAM
GPU2
1
3
4
この計算をピクセルごとにやるので遅い
(一回の計算は大したことないが、一秒間に256 x 240 x 60 回やる)
44
Cartridge
アルゴリズム改善: 実装
• スクリーンを事前描画して、変更部分のみ更新する
タイル配置データ
パレット配置データ
VRAM
GPU
screen buffer
VRAMが書き換わった 変更部分のみ更新
フレームごとに
ビデオ出力
※この説明はイメージです 45
画像データ
Cartridge
• 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
• 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
その他の最適化
• アルゴリズム最適化でも漸近的計算量だけを見ない
– 例: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
ここから闇
• ここまではプログラムの綺麗さは比較的維持していた
• ここからは、プログラムを汚くして高速化する
– 普通のRubyプログラマがマネするべき手法ではありません
– Rubyインタプリタの高速化の余地を図る研究です
49
™ 手動メソッド展開
• メソッド呼び出しは遅いので展開する
(当然ボトルネックのみ)
while catchup?
inc_addr
end
while catchup?
@addr += 1
end
28 fps  40 fps
50
™ インスタンス変数のローカル変数化
• インスタンス変数アクセスが遅い
– ローカル変数に置き換える
while catchup?
@addr += 1
end
begin
addr = @addr
while catchup?
addr += 1
end
ensure
@addr = addr
end
40 fps  47 fps
51
• 典型的な実行パスを展開する
™ 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
各 ™ の効果
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
実装上の工夫
• 手動と言っても本当に手動にはしない
– 正規表現を使って自動的に変換する
• コードのインデントを頼りにパースする
– コードのインデントがちゃんとしている前提
– コードチェッカ Rubocop を使って、
コードのインデントが変になっていないことを検証する
src = File.read(__FILE__)
src.gsub!(/.../) { ... } # method inlining
src.gsub!(/.../) { ... } # ivar localization
eval(src)
54
アジェンダ
• 背景:Ruby3x3
• 事例:Optcarrot
• 最適化の心得
• プロファイラの使い方
• 最適化の具体例
• Ruby3x3 のためのベンチマーク
• 与太話
55
の前に、テスト用 の入手方法
• 有志がスクラッチから作ったROMがある
– http://www.romhacking.net/
– ベンチマーク用にはこちら(Lan master を使用)
• もう1つの手段:実際のカートリッジから抽出する
– このための回路を通称で「吸出し器」と呼ぶ
56
自作した吸出し器
57
自作した吸出し器
※吸出しプログラムも Ruby で書かれている
(arduino_fermata gem を利用) 58
Arduino + Firmata
自作シールド
(「ホンコン」ベース)
カートリッジ
接触悪い
パスコンなし
ベンチマーク(アルゴリズム改善)
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 的に重要)
こんなのでも動く
ベンチマーク(+闇)
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
考察
• JRuby 9k が最速: “Deoptimization” が有望
– レアケースを無視して最適化 JIT コンパイルしておく
– レアケースが発生したら、そこだけ JIT コンパイルしなおす
 卜部さんが検討中?
https://github.com/shyouhei/ruby/tree/deoptimization_base
• OMR はあまり速くない?
– JIT は望み薄?
• perf によると 50% が評価器だが、これは基本的な組み込みメソッド
の処理を含んでいる
– OMR はできたばかり
• チューニングの余地はあるし、opt_case_dispatch (case 文)の
最適化がまだっぽい
61
不完全な 実装との戦い
• mruby:
– require なし、module_function なし、 fiber なし、
モジュールのインクルードがなんか微妙
• topaz:
– String#tr や #% を使ったら異常終了することがある
• opal:
– パスが動的な require 禁止、binread を自力実装
• 適当な shim を作った
– shim 不要なのは MRI と JRuby 9.1.0.0 だけ
62
ベンチマークプログラム
• コード生成含めて 5000 行以下
• 非GUIモードならライブラリ一切不要
– miniruby(MRI開発用の不完全なRuby実装)でも動く
– GUIモードでは ruby-ffi 経由でSDL2 を使う
• 基本的な Ruby 機能しか使っていない
– ruby 1.8 / mruby / topaz / opal でも shim などで動く
• Ruby開発者がRubyの高速化を検証する流れ
– miniruby ビルド→ miniruby 実行
63
よく使われるベンチ:
• 30000行超
• セットアップ手順
– PostgreSQL の設定
– Redmine インストールと初期設定と初期データ登録
– Apache のインストールと設定
– Passenger のインストールと設定
• Ruby開発者がRubyの高速化を検証する流れ
– Ruby 全ビルド→インストール→(Apache再起動?)→
ab 実行
64
『手軽』なベンチマーク
• Ruby開発者にとって『手軽』であることが重要
• インストールやセットアップが不要
– git clone だけとコマンド一発で
標準的な設定とデータセットが準備できると理想
• コマンドラインから ruby foo.rb で起動・終了する
– 「このコマンドの実行時間を短縮せよ」だとわかりやすい
• 数秒くらいで終わり、毎回の実行時間が安定しているとより良い
– rake や rack 経由で実行するのはめんどい
• プロファイラやデバッガが使いにくい
• Ruby 開発者の Ruby は /usr/bin/ruby にはない
• 依存関係が少ないこと、そんなに大きくないこと、など
– miniruby で動くと理想(拡張ライブラリなし)
65
は適切なベンチマーク?
• 「遅いRubyには向いてないアプリでは?」
– もう Ruby はそこまで遅くない
– 「科学技術計算向き」とされる Python よりも
(コア自体は)速くなってきている
• 「にしても、エミュレータはRubyの目指すところか?」
– 個人的にはこういうのも書ける言語になってほしい
– 他のものを高速化してほしい人は、自分なりの
ベンチマークプログラムを作って公開してください
66
アジェンダ
• 背景:Ruby3x3
• 事例:Optcarrot
• 最適化の心得
• プロファイラの使い方
• 最適化の具体例
• Ruby 処理系ベンチマーク
• 与太話
67
プログラミング言語は道具?
• 「言語は道具。用途に応じて使い分けるべき。」
– 本当?
• 「言語は道具」?
– 自然言語は思考・意思を伝達する道具というが
– 思考・意思は言語と独立している?本当に?
• 「用途に応じて使い分けるべき」?
– 言語の習得はコストが高い
– 適応力がある人 or ニワカな人 or コードを書かない人
68
つの言語にこだわる つの理由
• 言語が「脳のキャッシュ」に載る
– リファレンスを見ずにプログラムできる
– ストレスなくプログラミングするために重要
– 多数の言語をこの状態にするのは(自分は)難しい
• 言語の「土地勘」が身につく
– その言語「らしい」書き方がわかる
– 「らしい」書き方だと、効率的なプログラムになったり、
バージョンアップ時のハマりを減らせたりする
– 言語機能の熟練度が予想できる(akr プロダクトは安心)
• 言語の「守備範囲」がよくわかる
– その言語でできることは思ったより広い
69
注意
• 他言語を勉強しなくていいわけではない
– 見識は広げるべき
– ただ、「広く浅く」だけでは見えてこないものもある
• 何が何でも 1 つの言語で完結させる必要はない
– 工夫しまくってファミコンエミュレータが書けたが
– 工夫せずに実速が出せる C 言語の方が適切なのは確か
– ただ、ちょっとした工夫で言語を変えせずに済む場合も多い
• エコシステムの存在はでかい
– 科学技術計算やるなら Python に戦ってもしょうがない
– 再発明のコストが新言語習得のコストを明らかに上回る
70
まとめ
• Rubyで高速プログラムを書く方法を紹介しました
– 背景:Ruby3x3
– 事例:Optcarrot
– 最適化の心得
– プロファイラの使い方
– 最適化の具体例
– Ruby 処理系ベンチマーク
– 与太話
71

More Related Content

What's hot

Twitterのsnowflakeについて
TwitterのsnowflakeについてTwitterのsnowflakeについて
Twitterのsnowflakeについて
moai kids
 

What's hot (20)

Vacuum徹底解説
Vacuum徹底解説Vacuum徹底解説
Vacuum徹底解説
 
ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開ヤフー社内でやってるMySQLチューニングセミナー大公開
ヤフー社内でやってるMySQLチューニングセミナー大公開
 
DockerコンテナでGitを使う
DockerコンテナでGitを使うDockerコンテナでGitを使う
DockerコンテナでGitを使う
 
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭するCEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
 
MongoDBが遅いときの切り分け方法
MongoDBが遅いときの切り分け方法MongoDBが遅いときの切り分け方法
MongoDBが遅いときの切り分け方法
 
DockerとPodmanの比較
DockerとPodmanの比較DockerとPodmanの比較
DockerとPodmanの比較
 
SpringBootTest入門
SpringBootTest入門SpringBootTest入門
SpringBootTest入門
 
Guide To AGPL
Guide To AGPLGuide To AGPL
Guide To AGPL
 
プログラムを高速化する話
プログラムを高速化する話プログラムを高速化する話
プログラムを高速化する話
 
Linux女子部 systemd徹底入門
Linux女子部 systemd徹底入門Linux女子部 systemd徹底入門
Linux女子部 systemd徹底入門
 
9/14にリリースされたばかりの新LTS版Java 17、ここ3年間のJavaの変化を知ろう!(Open Source Conference 2021 O...
9/14にリリースされたばかりの新LTS版Java 17、ここ3年間のJavaの変化を知ろう!(Open Source Conference 2021 O...9/14にリリースされたばかりの新LTS版Java 17、ここ3年間のJavaの変化を知ろう!(Open Source Conference 2021 O...
9/14にリリースされたばかりの新LTS版Java 17、ここ3年間のJavaの変化を知ろう!(Open Source Conference 2021 O...
 
何となく勉強した気分になれるパーサ入門
何となく勉強した気分になれるパーサ入門何となく勉強した気分になれるパーサ入門
何となく勉強した気分になれるパーサ入門
 
PlaySQLAlchemy: SQLAlchemy入門
PlaySQLAlchemy: SQLAlchemy入門PlaySQLAlchemy: SQLAlchemy入門
PlaySQLAlchemy: SQLAlchemy入門
 
ふつうのRailsアプリケーション開発
ふつうのRailsアプリケーション開発ふつうのRailsアプリケーション開発
ふつうのRailsアプリケーション開発
 
ジョブ管理でcronは限界があったので”Rundeck”を使ってハッピーになりました
ジョブ管理でcronは限界があったので”Rundeck”を使ってハッピーになりましたジョブ管理でcronは限界があったので”Rundeck”を使ってハッピーになりました
ジョブ管理でcronは限界があったので”Rundeck”を使ってハッピーになりました
 
例外設計における大罪
例外設計における大罪例外設計における大罪
例外設計における大罪
 
BuildKitの概要と最近の機能
BuildKitの概要と最近の機能BuildKitの概要と最近の機能
BuildKitの概要と最近の機能
 
Redisの特徴と活用方法について
Redisの特徴と活用方法についてRedisの特徴と活用方法について
Redisの特徴と活用方法について
 
Twitterのsnowflakeについて
TwitterのsnowflakeについてTwitterのsnowflakeについて
Twitterのsnowflakeについて
 
乗っ取れコンテナ!!開発者から見たコンテナセキュリティの考え方(CloudNative Days Tokyo 2021 発表資料)
乗っ取れコンテナ!!開発者から見たコンテナセキュリティの考え方(CloudNative Days Tokyo 2021 発表資料)乗っ取れコンテナ!!開発者から見たコンテナセキュリティの考え方(CloudNative Days Tokyo 2021 発表資料)
乗っ取れコンテナ!!開発者から見たコンテナセキュリティの考え方(CloudNative Days Tokyo 2021 発表資料)
 

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

Asakusa バッチの運用を支える技術
Asakusa バッチの運用を支える技術Asakusa バッチの運用を支える技術
Asakusa バッチの運用を支える技術
KinebuchiTomo
 
Gitと出会って人生変わった テックヒルズ2013-03-22
Gitと出会って人生変わった テックヒルズ2013-03-22Gitと出会って人生変わった テックヒルズ2013-03-22
Gitと出会って人生変わった テックヒルズ2013-03-22
Shota Umeda
 
ピクサー USD 入門 新たなコンテンツパイプラインを構築する
ピクサー USD 入門 新たなコンテンツパイプラインを構築するピクサー USD 入門 新たなコンテンツパイプラインを構築する
ピクサー USD 入門 新たなコンテンツパイプラインを構築する
Takahito Tejima
 
高速な暗号実装のためにしてきたこと
高速な暗号実装のためにしてきたこと高速な暗号実装のためにしてきたこと
高速な暗号実装のためにしてきたこと
MITSUNARI Shigeo
 

Similar to Ruby で高速なプログラムを書く (20)

ETロボコン2020 競技会場システムのおはなし
ETロボコン2020 競技会場システムのおはなしETロボコン2020 競技会場システムのおはなし
ETロボコン2020 競技会場システムのおはなし
 
Play framework 2.0のおすすめと1.2からのアップグレード
Play framework 2.0のおすすめと1.2からのアップグレードPlay framework 2.0のおすすめと1.2からのアップグレード
Play framework 2.0のおすすめと1.2からのアップグレード
 
らくちん Go言語
らくちん Go言語らくちん Go言語
らくちん Go言語
 
Getting Started GraalVM / GraalVM超入門 #jjug_ccc #ccc_c2
Getting Started GraalVM / GraalVM超入門 #jjug_ccc #ccc_c2Getting Started GraalVM / GraalVM超入門 #jjug_ccc #ccc_c2
Getting Started GraalVM / GraalVM超入門 #jjug_ccc #ccc_c2
 
Getting Started GraalVM (再アップロード)
Getting Started GraalVM (再アップロード)Getting Started GraalVM (再アップロード)
Getting Started GraalVM (再アップロード)
 
Play jjug2012spring
Play jjug2012springPlay jjug2012spring
Play jjug2012spring
 
Zynga
ZyngaZynga
Zynga
 
Aws privte20110406 arai
Aws privte20110406 araiAws privte20110406 arai
Aws privte20110406 arai
 
Asakusa バッチの運用を支える技術
Asakusa バッチの運用を支える技術Asakusa バッチの運用を支える技術
Asakusa バッチの運用を支える技術
 
アカツキはどのようにAWSを活用しているか #jawsug
アカツキはどのようにAWSを活用しているか #jawsugアカツキはどのようにAWSを活用しているか #jawsug
アカツキはどのようにAWSを活用しているか #jawsug
 
Gitと出会って人生変わった テックヒルズ2013-03-22
Gitと出会って人生変わった テックヒルズ2013-03-22Gitと出会って人生変わった テックヒルズ2013-03-22
Gitと出会って人生変わった テックヒルズ2013-03-22
 
2012-03-08 MSS研究会
2012-03-08 MSS研究会2012-03-08 MSS研究会
2012-03-08 MSS研究会
 
明日使える超高速Ruby - RXbyak (Mitaka.rb #5)
明日使える超高速Ruby - RXbyak (Mitaka.rb #5)明日使える超高速Ruby - RXbyak (Mitaka.rb #5)
明日使える超高速Ruby - RXbyak (Mitaka.rb #5)
 
ピクサー USD 入門 新たなコンテンツパイプラインを構築する
ピクサー USD 入門 新たなコンテンツパイプラインを構築するピクサー USD 入門 新たなコンテンツパイプラインを構築する
ピクサー USD 入門 新たなコンテンツパイプラインを構築する
 
Craft CMSに最適なサーバはどんな環境?
Craft CMSに最適なサーバはどんな環境?Craft CMSに最適なサーバはどんな環境?
Craft CMSに最適なサーバはどんな環境?
 
Web socket and gRPC
Web socket and gRPCWeb socket and gRPC
Web socket and gRPC
 
高速な暗号実装のためにしてきたこと
高速な暗号実装のためにしてきたこと高速な暗号実装のためにしてきたこと
高速な暗号実装のためにしてきたこと
 
Web時代の大富豪的プログラミングのススメ
Web時代の大富豪的プログラミングのススメWeb時代の大富豪的プログラミングのススメ
Web時代の大富豪的プログラミングのススメ
 
Javaはどのように動くのか~スライドでわかるJVMの仕組み
Javaはどのように動くのか~スライドでわかるJVMの仕組みJavaはどのように動くのか~スライドでわかるJVMの仕組み
Javaはどのように動くのか~スライドでわかるJVMの仕組み
 
openFrameworks Workshop in Kanazawa v001
openFrameworks Workshop in Kanazawa v001openFrameworks Workshop in Kanazawa v001
openFrameworks Workshop in Kanazawa v001
 

More from mametter

クックパッド春の超絶技巧パンまつり 超絶技巧プログラミング編 資料
クックパッド春の超絶技巧パンまつり 超絶技巧プログラミング編 資料クックパッド春の超絶技巧パンまつり 超絶技巧プログラミング編 資料
クックパッド春の超絶技巧パンまつり 超絶技巧プログラミング編 資料
mametter
 
Enjoy Ruby Programming in IDE and TypeProf
Enjoy Ruby Programming in IDE and TypeProfEnjoy Ruby Programming in IDE and TypeProf
Enjoy Ruby Programming in IDE and TypeProf
mametter
 
A Static Type Analyzer of Untyped Ruby Code for Ruby 3
A Static Type Analyzer of Untyped Ruby Code for Ruby 3A Static Type Analyzer of Untyped Ruby Code for Ruby 3
A Static Type Analyzer of Untyped Ruby Code for Ruby 3
mametter
 
Cookpad Hackarade #04: Create Your Own Interpreter
Cookpad Hackarade #04: Create Your Own InterpreterCookpad Hackarade #04: Create Your Own Interpreter
Cookpad Hackarade #04: Create Your Own Interpreter
mametter
 

More from mametter (20)

error_highlight: User-friendly Error Diagnostics
error_highlight: User-friendly Error Diagnosticserror_highlight: User-friendly Error Diagnostics
error_highlight: User-friendly Error Diagnostics
 
TRICK 2022 Results
TRICK 2022 ResultsTRICK 2022 Results
TRICK 2022 Results
 
クックパッド春の超絶技巧パンまつり 超絶技巧プログラミング編 資料
クックパッド春の超絶技巧パンまつり 超絶技巧プログラミング編 資料クックパッド春の超絶技巧パンまつり 超絶技巧プログラミング編 資料
クックパッド春の超絶技巧パンまつり 超絶技巧プログラミング編 資料
 
Enjoy Ruby Programming in IDE and TypeProf
Enjoy Ruby Programming in IDE and TypeProfEnjoy Ruby Programming in IDE and TypeProf
Enjoy Ruby Programming in IDE and TypeProf
 
TypeProf for IDE: Enrich Development Experience without Annotations
TypeProf for IDE: Enrich Development Experience without AnnotationsTypeProf for IDE: Enrich Development Experience without Annotations
TypeProf for IDE: Enrich Development Experience without Annotations
 
Ruby 3の型解析に向けた計画
Ruby 3の型解析に向けた計画Ruby 3の型解析に向けた計画
Ruby 3の型解析に向けた計画
 
emruby: ブラウザで動くRuby
emruby: ブラウザで動くRubyemruby: ブラウザで動くRuby
emruby: ブラウザで動くRuby
 
Type Profiler: Ambitious Type Inference for Ruby 3
Type Profiler: Ambitious Type Inference for Ruby 3Type Profiler: Ambitious Type Inference for Ruby 3
Type Profiler: Ambitious Type Inference for Ruby 3
 
型プロファイラ:抽象解釈に基づくRuby 3の静的解析
型プロファイラ:抽象解釈に基づくRuby 3の静的解析型プロファイラ:抽象解釈に基づくRuby 3の静的解析
型プロファイラ:抽象解釈に基づくRuby 3の静的解析
 
Ruby 3の型推論やってます
Ruby 3の型推論やってますRuby 3の型推論やってます
Ruby 3の型推論やってます
 
マニアックなRuby 2.7新機能紹介
マニアックなRuby 2.7新機能紹介マニアックなRuby 2.7新機能紹介
マニアックなRuby 2.7新機能紹介
 
A Static Type Analyzer of Untyped Ruby Code for Ruby 3
A Static Type Analyzer of Untyped Ruby Code for Ruby 3A Static Type Analyzer of Untyped Ruby Code for Ruby 3
A Static Type Analyzer of Untyped Ruby Code for Ruby 3
 
A Plan towards Ruby 3 Types
A Plan towards Ruby 3 TypesA Plan towards Ruby 3 Types
A Plan towards Ruby 3 Types
 
Ruby 3 の型解析に向けた計画
Ruby 3 の型解析に向けた計画Ruby 3 の型解析に向けた計画
Ruby 3 の型解析に向けた計画
 
A Type-level Ruby Interpreter for Testing and Understanding
A Type-level Ruby Interpreter for Testing and UnderstandingA Type-level Ruby Interpreter for Testing and Understanding
A Type-level Ruby Interpreter for Testing and Understanding
 
本番環境で使える実行コード記録機能
本番環境で使える実行コード記録機能本番環境で使える実行コード記録機能
本番環境で使える実行コード記録機能
 
Transcendental Programming in Ruby
Transcendental Programming in RubyTranscendental Programming in Ruby
Transcendental Programming in Ruby
 
Cookpad Hackarade #04: Create Your Own Interpreter
Cookpad Hackarade #04: Create Your Own InterpreterCookpad Hackarade #04: Create Your Own Interpreter
Cookpad Hackarade #04: Create Your Own Interpreter
 
Ruby 3のキーワード引数について考える
Ruby 3のキーワード引数について考えるRuby 3のキーワード引数について考える
Ruby 3のキーワード引数について考える
 
TRICK 2018 results
TRICK 2018 resultsTRICK 2018 results
TRICK 2018 results
 

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