20
6

More than 5 years have passed since last update.

Rubyの末尾呼び出し最適化を試す

Posted at

どうも寝落ちしました。rsnniです。
Classi Advent Calendar 2017 17日目です。

Rubyも末尾呼び出し最適化をサポートしていると聞いたので試してみました。

末尾呼び出し最適化?

例えば、再帰で階乗計算する際に以下のように書いたとします。

def fact(x)
  return 1 if x == 0
  fact(x - 1) * x
end

この場合、再帰呼び出しをしたfactの結果にxをかけているため、
factの計算後に呼び出し元に戻さなければなりません。
これによって深い再帰になるとスタックが溢れる事になります。

そこで、以下のように再帰呼び出しを計算の最後に持ってこれるように書き直します。

def fact_tail_call(x, m = 1)
  return m if x == 0
  fact_tail_call(x - 1, x * m)
end

このように書き直した時に、トップレベル以降の呼び出し元に戻す必要がなくなる事から、
処理系がスタックを積む事なく最適化してくれます。

試してみる

デフォルトではサポートしておらず、コンパイルオプションを指定する必要がるようです...

RubyVM::InstructionSequence.compile_option = {
  :tailcall_optimization => true,
  :trace_instruction => false
}

RubyVM::InstructionSequence.new(<<-EOS).eval

def fact(x)
  return 1 if x == 0
  fact(x - 1) * x
end

def fact_tail_call(x, m = 1)
  return m if x == 0
  fact_tail_call(x - 1, x * m)
end

EOS

末尾呼び出しでない再帰ではスタックオーバーフローが発生しますが、

irb(main):019:0> fact(10000)
SystemStackError: stack level too deep

末尾再帰では最適化され計算できる事が確認できます。

irb(main):020:0> fact_tail_call(10000)
=> 2846259680917...
20
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
6