問題は、ルーチンがあることですn+get_sum(n-1)
。n の公約数が 3 または 5 の場合、Ruby はこのルーチンに進みます。これは末尾再帰では最適化できません。Ruby は、現在のフレームを保持して現在のフレームを保持する必要があり、計算さn
れたときにのみ、get_sum(n-1)
Ruby はフレームに戻って破棄できます。
一般に、スタック領域は高価です。オペレーティング システムは、プロセスごとに約 2M (おそらくそれ以上) のスタック スペースを予約します。これはすぐに消費できます。あなたのコードでは、(9999/3 + 9999/5)
再帰呼び出しがすべてのスタック スペースを消費しました。
再帰的なプログラミング パターンを維持したい場合は、メソッド呼び出しの動作を再帰的 (現在の状態) から反復的に変更する必要があります。
def get_sum(n, sum = 0)
return sum if n < 1
if n % 3 == 0 || n % 5 == 0
get_sum(n - 1, sum + n)
else
get_sum(n - 1, sum)
end
end
ただし、Ruby の末尾再帰の最適化はそれほど強力ではないようです。上記のコード サンプルには、2 つの異なるget_sum()
テール コールがあります。Ruby はこれを最適化しません。2 つの呼び出しを 1 つに書き直す必要があります。
さらに、提供した微調整でこれを機能させるにはRubyVM
、実行時にメソッド定義をロードする必要があります。これは、RubyVM
微調整が実行時に行われるためです。メソッド定義を同じファイルに直接配置することはできません。そうしないと、コンパイル時にメソッドが解析され、最適化が有効になりません。
get_sum
微調整後に別のファイルとrequire
そのライブラリに入れることができますRubyVM
:
# file: test_lib.rb
def get_sum(n, sum = 0)
if n < 1
sum
else
get_sum(n - 1, sum + (n % 3 == 0 || n % 5 == 0 ? n : 0))
end
end
# eof
# file: test.rb
RubyVM::InstructionSequence.compile_option = {
:tailcall_optimization => true,
:trace_instruction => false
}
require_relative 'test_lib'
puts get_sum(999999)
または、eval
実行時に String 内のメソッド定義を評価するために使用します。
RubyVM::InstructionSequence.compile_option = {
:tailcall_optimization => true,
:trace_instruction => false
}
eval <<END
def get_sum(n, sum = 0)
if n < 1
sum
else
get_sum(n - 1, sum + (n % 3 == 0 || n % 5 == 0 ? n : 0))
end
end
END
puts get_sum(990000)