はじめに
RubyVMの様子を観察したいではRubyVMのスタックを表示させつつ命令単位でステップ実行する方法を紹介しました。
これらの命令列は高速化のためコンパイル時に様々な最適化が施されています。 普段はもちろん無効化することはありませんが、純粋なRubyVMの動作を理解するために邪魔だったので無効化する方法を調べました。
Rubyプログラム内で無効化する
Rubyプログラム内で最適化を無効化する方法です。
RubyVM::InstructionSequence
にオプションとして指定します。
次のような方法で指定できます。
# デフォルトのオプションを指定する
RubyVM::InstructionSequence.compile_option = options
# Iseqインスタンス生成時に指定する
iseq = RubyVM::InstructionSequence.new("1 + 2", nil, nil, 1, options)
iseq = RubyVM::InstructionSequence.compile_file(file_path, options)
最適化あり/なしではこのような変化があります。
# あり
$ ruby -e 'puts RubyVM::InstructionSequence.new("1 + 2").disasm'
== disasm: #<ISeq:<compiled>@<compiled>>================================
0000 trace 1 ( 1)
0002 putobject_OP_INT2FIX_O_1_C_
0003 putobject 2
0005 opt_plus <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache>
0008 leave
# なし
$ ruby -e 'puts RubyVM::InstructionSequence.new("1 + 2", nil, nil, 1, false).disasm'
== disasm: #<ISeq:<compiled>@<compiled>>================================
0000 putobject 1 ( 1)
0002 putobject 2
0004 send <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache>, nil
0008 leave
最適化ありだと1.+(2)
が通常のメソッド呼び出しではなく、opt_plus
という最適化命令になっていることがわかります。
ビルド時に無効化する
これは結構無理やりな感じです。
Ruby実行前にコンパイルオプションを指定するやり方を調べたんですが、ruby
コマンドのオプションにも./configure
にもそれっぽいのがなかったのでソースコードを書き換えました。
これをビルドすると、こんな感じで最初から最適化が無効化されてることがわかります。
$ ./ruby --disable-gems -e 'puts RubyVM::InstructionSequence.new("1 + 2").disasm'
== disasm: #<ISeq:<compiled>@<compiled>>================================
0000 putobject 1 ( 1)
0002 putobject 2
0004 send <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache>, nil
0008 leave