2. Ebisu.rb #12 発表資料 「Fiber の使いどころ」
1RubyKaigi 2017 に行ってきました。
下記の2つで Fiber について取り上げられていました。
Fiber in the 10th year
笹田耕一さん
How to write synchronization mechanisms for Fiber
関将俊(せき・まさとし)さん
3. Ebisu.rb #12 発表資料 「Fiber の使いどころ」
2Fiber in the 10th year
https://www.slideshare.net/KoichiSasada/fiber-in-the-10th-year
Fiber の実用例
内部イテレータから外部イテレータ
エージェントシミュレーション
ノンブロッキングIOスケジューラ
Fiber の歴史
コンテキストスイッチの軽量化
Auto Fiber の提案
4. Ebisu.rb #12 発表資料 「Fiber の使いどころ」
3How to write synchronization mechanisms for Fiber
https://speakerdeck.com/m_seki/how-to-write-synchronization-
mechanisms-for-fiber
Fiber を実装したときの知見の話
Process の方が Thread よりほとんどの人にとって良い
Thread の方が Fiber よりほとんどの人にとって良い
Fibonacci (Enumerator)
Rdv (SizedQueue、Channel)
Multiplexer
9. Ebisu.rb #12 発表資料 「Fiber の使いどころ」
単純な実装 8
a = 0; b = 1
loop do
a, b = b, a + b
break if 100 < a
puts a
end
■ (1) もっとも単純な実装 ■ (2) 多重代入の利用
もっとも単純なフィボナッチ数列の Ruby による実装
ポイント: ループ、多重代入、while の構文
a = 0; b = 1
loop do
tmp = a
a = b
b = tmp + b
break if 100 < a
puts a
end
a = 0; b = 1
while a < 100
puts b
a, b = b, a + b
end
■ (3) while の利用
10. Ebisu.rb #12 発表資料 「Fiber の使いどころ」
ブロック付メソッド 9
■ (4) ブロック付メソッド呼び出し ■ 処理の実施順
yield キーワードにより、ブロック引数に値を渡せる。
ブロック付メソッド呼出しにより、
値の生成と、値の利用の処理を分離できる
def fibonacci
a = 0; b = 1
loop do
a, b = b, a + b
yield a
end
end
n = 1
fibonacci do |i|
break if 100 < a
puts “#{n} #{i}"
n += 1
end
①
②
③
⑤
⑦
⑤ → ①
→ ② → ⑦ → ③
→ ② → ⑦ → ③
→ ② → ⑦ → ③
・・・ (以下おなじ)
11. Ebisu.rb #12 発表資料 「Fiber の使いどころ」
クラスの利用 10
■ (5) クラスの利用
クラスを利用し、内部状態をインスタンス変数に持たせる方法もある
この場合は、next を実行したときに内部状態が変化する。
class Fibonacci
def initialize
@a = 0; @b = 1
end
def next
@a, @b = @b, @a + @b
return @a
end
end
fibonacci = Fibonacci.new
loop do
i = fibonacci.next()
break if i > 100
puts i
end
インスタンス変数の初期化
次の値の生成
12. Ebisu.rb #12 発表資料 「Fiber の使いどころ」
クロージャ 11
■ (6) クロージャの利用
クロージャという機能により、ブロックの中から
ネストの外側の変数を参照できる。
内部状態を持つ関数を簡易に作ることができる。
fibonacci = proc do
a = 0; b = 1
lambda do
a, b = b, a + b
return a
end
end.call()
loop do
i = fibonacci.()
break if i > 100
puts i
end
スコープを導入するためのクロージャ
外側のクロージャを呼ぶ出すための Proc#call
内側のクロージャを作るための lambda
中で return を使いたいので 「 lambda 」にしている。
クロージャを呼び出し、Fibonacci 数列の次の値を取得する
13. Ebisu.rb #12 発表資料 「Fiber の使いどころ」
Ruby 1.8: Generator の利用 12
■ (7) Generator の利用
Ruby 1.8 には、標準ライブラリに Generator があった
内部実装に callcc を使うので、ちょいと遅い
require 'generator'
g = Generator.new do |yielder|
a = 0; b = 1
loop do
a, b = b, a + b
yielder.yield a
end
end
g.each do |i|
break if i > 100
puts i
end
Generator が生成する項を順に取得する
次の値を生成する
14. Ebisu.rb #12 発表資料 「Fiber の使いどころ」
Ruby 1.9: Enumerator の利用 13
■ (8) Enumerator の利用
Ruby 1.9 では Enumerator を利用可能
内部実装に Fiber を使うため、高速
Enumerable モジュールのメソッド(each_cons など)を利用可能
e = Enumerator.new do |yielder|
a = 0; b = 1
loop do
a, b = b, a + b
yielder << a
end
end
e.each do |i|
break if i > 100
puts i
end
Enumerator が生成する項を順に取得する
次の生成する値を yield する
Enumerator による実装は、
利用側のコード行数がコンパクト
になる。
15. Ebisu.rb #12 発表資料 「Fiber の使いどころ」
(参考) Enumerator と Fiber 14
e = Enumerator.new do |yielder|
a = 0; b = 1
loop do
a, b = b, a + b
yielder << a
end
end
e.each do |i|
break if 100 < i
puts i
end
■ (8) 《再掲》 Enumerator の例
fib = Fiber.new do
a = 0; b = 1
loop do
a, b = b, a + b
Fiber.yield a
end
end
def fib.each
loop do
yield self.resume
end
end
fib.each do |i|
break if 100 > i
puts i
end
■ 《参考》 (9) Fiberの例
Enumerator は Fiber を活用するデザインパターンの1つ
Fiber における難しい概念を隠ぺい
Fiber.yield => Enumerator::Yielder#<< 、 Enumerator#each による列挙
Enumerator を使いこなせれば、十分 Fiber のメリットを活用できる
《参考: Fiber にできて、Enumerator でできないこと》
親 Fiber から 子Fiber へのデータの送付(Fiber.yield の返り値の利用)
これが必要。
だけど、これが
難しい。(汗)
Enumerator の
内部実装まで
考えなくていい
16. Ebisu.rb #12 発表資料 「Fiber の使いどころ」
Enumerator はベンリ 15
fib = Enumerator.new do |yielder|
a = 0; b = 1
loop do
a, b = b, a + b
yielder << a
end
end
fib.each do |i|
break if 100 < I
puts i
end
fib.each_cons(2) do |i, j|
break if 100 < i
puts "#{i} #{j}"
end
■ (10) Enumerator の例
def fibonacci
a = 0; b = 1
loop do
a, b = b, a + b
yield a
end
end
fibonacci do |i|
break if 100 < i
puts i
end
prev = nil
fibonacci do |i|
break if 100 < i
puts "#{prev} #{i}" if prev
prev = i
end
■ 《参考》 (11) ブロック付メソッド呼び出しの例
Enumerator なら Enumerable の便利メソッドを使える。
ブロック付メソッド呼び出しは単純なイテレーションならいいけど。。。
Enumerator はオブジェクトなので、使い回しが簡単。
引数にしたり、返り値にしたり、インスタンス変数に格納したり
Enumerator オブジェクトの生成も慣れれば簡単。
慣れれば、
カンタン
面倒 ! !
Enumerable
の便利メソッド
を使い放題
単純なイテレーション
ならいいけど・・・。
17. Ebisu.rb #12 発表資料 「Fiber の使いどころ」
Enumerable#lazy の利用 16
fib = Enumerator.new do |yielder|
a = 0; b = 1
loop do
a, b = b, a + b
yielder << a
end
end
fib.select do |i|
i < 100
end.
take(10).each do |i|
puts i
end
■ (12) うまく動かない例
fib = Enumerator.new do |yielder|
a = 0; b = 1
loop do
a, b = b, a + b
yielder << a
end
end
fib.lazy.select do |i|
i < 100
end.take(10).each do |i|
puts i
end
■ (13) Enumerable#lazy を利用する例
Ruby 2.0.0 から、Enumerable#lazy が利用可能となる
Enumerable#lazy を使うと、遅延評価になり、無限を無限のまま扱える
宇宙ヤバい
Ruby 1.9 でも gem でインストール可能
@yhara さんの作品
ここで、無限ループに
なり、うまく動かない。
※ take_while を
使う方法もある。
Enumerable#lazy
を使えば、正しく
期待通りに動作する
30. Ebisu.rb #12 発表資料 「Fiber の使いどころ」
電卓の Transformer の実装
LLVM 関係の処理は、LLVMBuilder クラスに外出し
rule を記述するだけで比較的簡単に実装可能
29
class LLVMTransformer <Parslet::Transform
b = @@builder = LLVMBuilder.new
rule(:int => simple(:n)){
b.int n
}
rule(:left => simple(:l),
:right => simple(:r),
:op => simple(:op)){
b.calc op, l, r
}
rule(:expr => simple(x)){
b.ret x
}
def do(tree)
apply(tree)
@@builder.dump
end
end
パーサによって生成された
"int" 要素を処理。
LLVM の整数に変換。
パーサによって生成された
"left"、 "right"、 "op" の
要素を持つサブツリーに対して
処理を行う。
LLVM の命令に変換する。
31. Ebisu.rb #12 発表資料 「Fiber の使いどころ」
電卓の LLVMBuilder の実装
LLVMBuilder では LLVM の命令を生成する。
building_loop で rule からの指示を待つ
30
class LLVMBuilder
def initialize
@fiber = Fiber.new do
@module = LLVM::Module.new("calculator")
@module.functions.add("add", [], LLVM::Int) do |f,|
bb = f.basic_blocks.append("entry")
bb.build do |builder|
building_loop builder
end
end
end
@fiber.resume
end
...
end
新しい Fiber を生成している。
1. Module の生成
2. Function の追加
3. Basic Block の追加
4. Builder を使い、LLVM 命令を追加
building_loop まで Fiber を実行させておく。
32. Ebisu.rb #12 発表資料 「Fiber の使いどころ」
電卓の LLVMBuilder の実装 31
def building_loop builder
llvm_ir = nil
loop do
op, l, r = Fiber.yield llvm_ir
case op
when "+"
llvm_ir = builder.add l, r
when "-"
llvm_ir = builder.sub l, r
when '*'
llvm_ir = builder.mul l, r
when "/"
llvm_ir = builder.sdiv l, r
when :exit
llvm_ir = builder.ret l
break
end
end
end
Fiber から呼出し元への返り値みたいなもの。
Fiber なので、Fiber.yield の引数で処理結果を
渡して呼出し元に処理を戻す。
呼出し元から来る引数みたいなもの。
Fiber なので、Fiber.yield の返り値で
呼出し元から受け取る。
四則演算の LLVM
の命令を生成
計算結果を return する
LLVM の命令を生成
33. Ebisu.rb #12 発表資料 「Fiber の使いどころ」
電卓の LLVMBuilder の実装
Fiber の処理を calc 、ret というメソッドで隠ぺい
calc +、-、×、÷ の四則演算を LLVM の処理に変換する
ret 処理結果を LLVM の関数の返り値にする。
32
def int n
LLVM.Int n.to_i
end
def calc op, l, r
@fiber.resume op, l, r
end
def ret x
@fiber.resume :exit, x
end
def dump
@module.dump
end
Fiber の外から、演算を受け取って、
Fiber に渡す。
さらに Fiber での処理結果を返す。
生成された LLVM の計算プログラムを
出力する。
Ruby の整数を LLVM の整数に
変換する。
処理を終了し、演算結果を返り値
として返す