インスタンス変数アクセスの最適化
Rubyのインスタンス変数はRubyオブジェクトのプロパティの一つ
名前文字列をキーにしてアクセスできる
素朴に実装するとHashMap<Name, Value> 的な感じになるがそれだと遅い
monorubyでの実装
クラスに名前からID番号を引ける表を持たせる
各オブジェクトには配列を持たせ、ID番号をインデックスとしてアクセスする
ペイロードのタイプ(OBJECTかどうか)によってアクセスの仕方が異なる
VMが実行時に ①親オブジェクトのクラスID ②インスタンス変数のID番号 をトレース情報としてバイトコードに書き込んでいるので、JITコンパイル時にはその情報を利用する
code:Ruby
@a = 42
@a
code:txt
:00001 %1 = 42: i32
movabs rax,0x55 # 42のRubyオブジェクト
mov rdi,QWORD PTR r14-0x18 # rdi := self movabs rdi,0x4
test rax,rax # 該当スロットに値が格納されているかどうかをチェック
cmove rax,rdi # 値が無かったらnil
mov r15,rax
code:Ruby
class MyArray < Array
def f
@a = 42
@a
end
end
MyArray.new.f
code:txt
:00001 %1 = 42: i32
movabs rax,0x55 # 42のRubyオブジェクト
mov rdi,QWORD PTR r14-0x18 # rdi := self test rdx,rdx # check VarTable is not null
je 0xfff2825
cmp QWORD PTR rdx+0x8,0x0 # check capa is not 0 je 0xfff2825
cmp QWORD PTR rdx+0x10,0x0 # check IvarId(0) < len jle 0xfff2825
mov QWORD PTR rdx,rax # @a = 42 mov rdi,QWORD PTR r14-0x18 # rdi := self movabs rax,0x4
test rdx,rdx # check VarTable is not null
je 0x8c
cmp QWORD PTR rdx+0x8,0x0 # check capa is not 0 je 0x8c
cmp QWORD PTR rdx+0x10,0x0 # check IvarId(0) < len cmovg rax,QWORD PTR rdi+0*8 # %1 = @a 0x8c:
mov r15,rax
benchmark
code:txt
# Iteration per second (i/s)
| |3.3.0 --yjit| truffleruby-23.1.1| monoruby|
|:--------------------|-----------:|------------------:|------------:|
|vm_ivar | 198.369M| 20.922G| 2.151G|
|vm_ivar_get | 43.904| 410.537| 183.031|
|vm_ivar_set | 249.990M| 362.689M| 5.185G|
|vm_ivar_generic_get | 39.463M| 3.131G| 655.512M|
|vm_ivar_generic_set | 18.296M| 256.031M| 371.896M|
|vm_attr_ivar | 128.691M| 757.312M| 1.172G|
|vm_attr_ivar_set | 95.181M| 768.112M| 1.723G|
さらなる最適化
VarTableのcapacity・lengthは単調増加であることを利用するとチェックを省略できる