SlideShare a Scribd company logo
CPS & TCO
2016/05/19
0x64 Tales
#08 Func6onal Programming
Livesense Inc.
HORINOUCHI Masato
Con$nua$on-passing style とは
継続渡しスタイル (CPS: Con)nua)on-passing style) とは、プログラ
ムの制御を継続を用いて陽に表すプログラミングスタイルのことで
ある。
継続渡しスタイルで書かれた関数は、通常の直接スタイル (direct
style) のように値を「返す」かわりに、「継続」を引数として陽に受
け取り、その継続に計算結果を渡す。継続とは、関数の計算結果を
受け取るための(一般には元の関数とは別の)関数のことである。
継続渡しスタイル - Wikipedia から引用
factorial (Scheme / direct style)
(define (fact n)
(if (zero? n)
1
(* n (fact (- n 1)))))
(fact 10)
factorial (Scheme / CPS)
(define (fact/cps n cont) # ← 引数 cont が継続
(if (zero? n)
(cont 1)
(fact/cps (- n 1) (lambda (x) (cont (* n x))))))
(fact/cps 10 (lambda (x) x))
factorial (Scheme / direct style / tail recursion)
(define (fact_tail n acc)
(if (zero? n)
acc
(fact_tail (- n 1) (* n acc))))
(fact_tail 10 1)
benchmark result (1,0003mes 1000!)
• non tail call
• 0.767s
• cps
• 0.854s
• tail call
• 0.819s
• まさかの非末尾呼出版が最も速い。
• CPS は呼び出し毎に lambda object 生成するから遅いのかな。
Stack Overflow (Scheme)
• 各パターンとも 100000! でも Stack Overflow しない。
• Scheme は末尾呼出最適化を行なうので CPS版と末尾呼出版が
overflow しないのはわかる。
• 非末尾呼出版でも overflow しないのは、CPS変換を自動的に行
なっているのだろうか?
factorial (Ruby / direct style / recursion)
def fact(n)
if n == 0
1
else
n * fact(n - 1)
end
end
fact(10)
factorial (Ruby / CPS)
def fact_cps(n, cont)
if n == 0
cont.call 1
else
fact_cps(n - 1, -> (x) { cont.call(n * x) })
end
end
fact_cps(10, -> (x) { x })
factorial (Ruby / direct style / tail recursion)
def fact_tail(n, acc = 1)
if n == 0
acc
else
fact_tail(n - 1, n * acc)
end
end
fact_tail(10)
benchmark
TIMES = 1000
FACT = 1000
Benchmark.bm 16 do |r|
r.report 'non tail call' do
TIMES.times do
fact(FACT)
end
end
r.report 'cps' do
TIMES.times do
fact_cps(FACT, -> (x) { x })
end
end
r.report 'tail call' do
TIMES.times do
fact_tail(FACT)
end
end
end
benchmark result (1,0003mes 1000!)
user system total real
non tail call 0.570000 0.010000 0.580000 ( 0.575446)
cps 1.220000 0.060000 1.280000 ( 1.280935)
tail call 0.650000 0.060000 0.710000 ( 0.705097)
• Scheme版と同様の傾向になった。
Stack Overflow (Ruby)
• 5000!
• 3パターンとも問題なし
• 10000!
• CPS版が stack level too deep
• 11000!
• 3パターンとも stack level too deep
Tail Call Op)miza)on とは
末尾呼出し最適化とは、末尾呼出しのコードを、戻り先を保存しないジャン
プに変換することによって、スタックの累積を無くし、効率の向上などを図
る手法である。
理論的には、継続渡しによる goto と同等のジャンプ処理では、手続きの呼出
し元の情報を保存する必要がないことに帰着する。この場合 return は goto
の特殊なケースで、ジャンプ先が呼出し元に等しいケースである。末尾最適化
があれば、手続きの再帰を行なう時でも、結果はループと等価な処理手順と
なり、どれほど深い再帰を行なってもスタックオーバーフローを起こさない。
末尾再帰 - Wikipedia から引用
Tail Call Op)miza)on in Ruby
• YARV だとオプションで TCO 有効にできる。
RubyVM::InstructionSequence.compile_option = {
tailcall_optimization: true,
trace_instruction: false
}
benchmark result (1,0003mes 1000! / TCO)
user system total real
non tail call 0.570000 0.010000 0.580000 ( 0.575446)
cps 1.220000 0.060000 1.280000 ( 1.280935)
tail call 0.650000 0.060000 0.710000 ( 0.705097)
↓ TCO on
user system total real
non tail call 0.560000 0.020000 0.580000 ( 0.578095)
cps 1.130000 0.050000 1.180000 ( 1.183501)
tail call 0.640000 0.040000 0.680000 ( 0.688400)
• CPS で約 8%改善、末尾呼出で 4%改善
Stack Overflow (Ruby / TCO)
• 10000!
• 3パターンとも問題なし
• 11000!
• 非末尾呼出版が stack level too deep
• 12000!
• CPS版が stack level too deep ← なぜ?
• 1000000! まで試したが、末尾呼出版では Stack Overflow しない (10分かかった)
TCO Problems
• (1) backtrace: Elimina3ng method frame means elimina3ng
backtrace.
• (2) settracefunc(): It is difficult to probe "return" event for tail-call
methods.
• (3) seman3cs: It is difficult to define tail-call in document (half is
joking, but half is serious)
Tail call op)miza)on: enable by default? から引用
まとめ
• CPS に置き換えるだけでは速くならない。
• YARV だと TCO 有効化ができる。
• TCO に関係なく 1.upto(FACT).inject(:*) だと Stack
Overflow しないのはヒミツ。
ご清聴ありがとうございました

More Related Content

PPTX
JavaScript関数のあれこれ
PPTX
条件分岐・繰り返し処理
PPT
始めよう!OpenStreetMap Developing
PDF
モナドをつくろう
PDF
Pfds20120304
PPT
言語処理系入門€9
PDF
JavaScript 勉強会 ― 変数・演算子・文
PDF
Rmq
JavaScript関数のあれこれ
条件分岐・繰り返し処理
始めよう!OpenStreetMap Developing
モナドをつくろう
Pfds20120304
言語処理系入門€9
JavaScript 勉強会 ― 変数・演算子・文
Rmq

What's hot (16)

PDF
2011年12月9日
PDF
Rubyの御先祖CLUのお話(原本)
PDF
Python vs ruby
PPTX
C# linq入門 意図編
PDF
Delimited Dynamic Binding
PDF
P5utda day3
PDF
V6でJIT・部分適用・継続
PDF
20150928楽しいlambda
PDF
JS 6th edition reading circle part 3
PDF
みんなで Swift 復習会での談笑用スライド – 4th #minna_de_swift
PDF
Rubyの御先祖CLUのお話(OSC 2011 Shimane LT 資料)
PDF
Whole Program Paths 等の紹介@PLDIr#3
PDF
代数的データ型をラムダ計算の中で表現する方法
PDF
kagamicomput201707
PDF
2011年11月18日
PPTX
サイ本読書会4章変数
2011年12月9日
Rubyの御先祖CLUのお話(原本)
Python vs ruby
C# linq入門 意図編
Delimited Dynamic Binding
P5utda day3
V6でJIT・部分適用・継続
20150928楽しいlambda
JS 6th edition reading circle part 3
みんなで Swift 復習会での談笑用スライド – 4th #minna_de_swift
Rubyの御先祖CLUのお話(OSC 2011 Shimane LT 資料)
Whole Program Paths 等の紹介@PLDIr#3
代数的データ型をラムダ計算の中で表現する方法
kagamicomput201707
2011年11月18日
サイ本読書会4章変数
Ad

Viewers also liked (11)

PPTX
¿Qué flores son apropiadas según la ocasión?
PDF
20160122142226271
DOCX
17th July 2016 - What is Praye...
PPT
Class 2 in class essay
PDF
L'interet de comparer des devis avant de créer son site internet
PPT
Guangzhou yiyun clothing co.,ltd
PPTX
Psp%20%28 personal%20software%20process%29
PDF
P630i p640i Zebra
PPTX
1 a 15 concept essay workshop
PDF
La Juste Dose. Journée Réseau VRAC #3
¿Qué flores son apropiadas según la ocasión?
20160122142226271
17th July 2016 - What is Praye...
Class 2 in class essay
L'interet de comparer des devis avant de créer son site internet
Guangzhou yiyun clothing co.,ltd
Psp%20%28 personal%20software%20process%29
P630i p640i Zebra
1 a 15 concept essay workshop
La Juste Dose. Journée Réseau VRAC #3
Ad

More from Masato HORINOUCHI (9)

PDF
Church Numerals
PDF
FM synthesis
PDF
Inside mml2wav.rb
PDF
Scheme Interpreter in Ruby
PDF
Clock / Timer
PDF
PDF
POSIX Threads
PDF
Elixir紹介
Church Numerals
FM synthesis
Inside mml2wav.rb
Scheme Interpreter in Ruby
Clock / Timer
POSIX Threads
Elixir紹介

CPS & CTO

  • 1. CPS & TCO 2016/05/19 0x64 Tales #08 Func6onal Programming Livesense Inc. HORINOUCHI Masato
  • 2. Con$nua$on-passing style とは 継続渡しスタイル (CPS: Con)nua)on-passing style) とは、プログラ ムの制御を継続を用いて陽に表すプログラミングスタイルのことで ある。 継続渡しスタイルで書かれた関数は、通常の直接スタイル (direct style) のように値を「返す」かわりに、「継続」を引数として陽に受 け取り、その継続に計算結果を渡す。継続とは、関数の計算結果を 受け取るための(一般には元の関数とは別の)関数のことである。 継続渡しスタイル - Wikipedia から引用
  • 3. factorial (Scheme / direct style) (define (fact n) (if (zero? n) 1 (* n (fact (- n 1))))) (fact 10)
  • 4. factorial (Scheme / CPS) (define (fact/cps n cont) # ← 引数 cont が継続 (if (zero? n) (cont 1) (fact/cps (- n 1) (lambda (x) (cont (* n x)))))) (fact/cps 10 (lambda (x) x))
  • 5. factorial (Scheme / direct style / tail recursion) (define (fact_tail n acc) (if (zero? n) acc (fact_tail (- n 1) (* n acc)))) (fact_tail 10 1)
  • 6. benchmark result (1,0003mes 1000!) • non tail call • 0.767s • cps • 0.854s • tail call • 0.819s • まさかの非末尾呼出版が最も速い。 • CPS は呼び出し毎に lambda object 生成するから遅いのかな。
  • 7. Stack Overflow (Scheme) • 各パターンとも 100000! でも Stack Overflow しない。 • Scheme は末尾呼出最適化を行なうので CPS版と末尾呼出版が overflow しないのはわかる。 • 非末尾呼出版でも overflow しないのは、CPS変換を自動的に行 なっているのだろうか?
  • 8. factorial (Ruby / direct style / recursion) def fact(n) if n == 0 1 else n * fact(n - 1) end end fact(10)
  • 9. factorial (Ruby / CPS) def fact_cps(n, cont) if n == 0 cont.call 1 else fact_cps(n - 1, -> (x) { cont.call(n * x) }) end end fact_cps(10, -> (x) { x })
  • 10. factorial (Ruby / direct style / tail recursion) def fact_tail(n, acc = 1) if n == 0 acc else fact_tail(n - 1, n * acc) end end fact_tail(10)
  • 11. benchmark TIMES = 1000 FACT = 1000 Benchmark.bm 16 do |r| r.report 'non tail call' do TIMES.times do fact(FACT) end end r.report 'cps' do TIMES.times do fact_cps(FACT, -> (x) { x }) end end r.report 'tail call' do TIMES.times do fact_tail(FACT) end end end
  • 12. benchmark result (1,0003mes 1000!) user system total real non tail call 0.570000 0.010000 0.580000 ( 0.575446) cps 1.220000 0.060000 1.280000 ( 1.280935) tail call 0.650000 0.060000 0.710000 ( 0.705097) • Scheme版と同様の傾向になった。
  • 13. Stack Overflow (Ruby) • 5000! • 3パターンとも問題なし • 10000! • CPS版が stack level too deep • 11000! • 3パターンとも stack level too deep
  • 14. Tail Call Op)miza)on とは 末尾呼出し最適化とは、末尾呼出しのコードを、戻り先を保存しないジャン プに変換することによって、スタックの累積を無くし、効率の向上などを図 る手法である。 理論的には、継続渡しによる goto と同等のジャンプ処理では、手続きの呼出 し元の情報を保存する必要がないことに帰着する。この場合 return は goto の特殊なケースで、ジャンプ先が呼出し元に等しいケースである。末尾最適化 があれば、手続きの再帰を行なう時でも、結果はループと等価な処理手順と なり、どれほど深い再帰を行なってもスタックオーバーフローを起こさない。 末尾再帰 - Wikipedia から引用
  • 15. Tail Call Op)miza)on in Ruby • YARV だとオプションで TCO 有効にできる。 RubyVM::InstructionSequence.compile_option = { tailcall_optimization: true, trace_instruction: false }
  • 16. benchmark result (1,0003mes 1000! / TCO) user system total real non tail call 0.570000 0.010000 0.580000 ( 0.575446) cps 1.220000 0.060000 1.280000 ( 1.280935) tail call 0.650000 0.060000 0.710000 ( 0.705097) ↓ TCO on user system total real non tail call 0.560000 0.020000 0.580000 ( 0.578095) cps 1.130000 0.050000 1.180000 ( 1.183501) tail call 0.640000 0.040000 0.680000 ( 0.688400) • CPS で約 8%改善、末尾呼出で 4%改善
  • 17. Stack Overflow (Ruby / TCO) • 10000! • 3パターンとも問題なし • 11000! • 非末尾呼出版が stack level too deep • 12000! • CPS版が stack level too deep ← なぜ? • 1000000! まで試したが、末尾呼出版では Stack Overflow しない (10分かかった)
  • 18. TCO Problems • (1) backtrace: Elimina3ng method frame means elimina3ng backtrace. • (2) settracefunc(): It is difficult to probe "return" event for tail-call methods. • (3) seman3cs: It is difficult to define tail-call in document (half is joking, but half is serious) Tail call op)miza)on: enable by default? から引用
  • 19. まとめ • CPS に置き換えるだけでは速くならない。 • YARV だと TCO 有効化ができる。 • TCO に関係なく 1.upto(FACT).inject(:*) だと Stack Overflow しないのはヒミツ。