背景
前回の記事でCrystalのベンチマークを行った。
http://qiita.com/irxground/items/cd7368444caa5e27ecdd
もちろんこれは「悪くなるだろう」と予想したから計測したのだが、全然そんなことはなかったので、実際に「なぜ悪くならなかったか」を調べてみた。
最近の機械語を吐く言語はたいてい、LLVMを利用しているので、LLVMの中間表現を読んでみるのが手っ取り早い。
結果
Crystalは引数の型が Int8 | Int16 | Int32 | Int64
のようなUnionだった場合、引数の型ごとに関数を生成するようだ。確かにこの方法なら実行時のパフォーマンス劣化は起こらない。
しかも実際に利用した型のみ生成されているので(つまり今回で言えば、Int8
やInt16
の分の関数は生成されない)安心して利用できる。
コード等
コード
前回の一部を利用
fib_example.cr
def fib_32(n : Int32)
if n < 2
n
else
fib_32(n - 1) + fib_32(n - 2)
end
end
def fib_signed(n : Int::Signed)
if n < 2
n
else
fib_signed(n - 1) + fib_signed(n - 2)
end
end
N = 20
pp fib_32(N)
pp fib_signed(N.to_i32)
pp fib_signed(N.to_i64)
生成
$ crystal build --emit llvm-ir --release fib_example.cr
生成されたLLVM中間表現
自分の手元だと中間表現は 12,715行もの巨大なコードになるので、ぱっと見圧倒されるが、LLVM中間表現上の関数名には元のCrystalの関数名が含まれるので、関数名で検索すれば簡単に見つかる。
ただし今回の例ではうまくいっているが(CrystalがやっているのかLLVMがやっているのかわからないが)関数のインライン展開はかなり行われるので、もし同じようにLLVM中間表現を読もうとしている人は注意したほうがいいと思う。
; (略)
; Function Attrs: nounwind readnone uwtable
define internal fastcc i32 @"*fib_32<Int32>:Int32"(i32 %n) #5 {
entry:
%0 = icmp slt i32 %n, 2
br i1 %0, label %exit, label %else
else: ; preds = %entry
%1 = add i32 %n, -1
%2 = tail call fastcc i32 @"*fib_32<Int32>:Int32"(i32 %1)
%3 = add i32 %n, -2
%4 = tail call fastcc i32 @"*fib_32<Int32>:Int32"(i32 %3)
%5 = add i32 %4, %2
ret i32 %5
exit: ; preds = %entry
ret i32 %n
}
; (略)
; Function Attrs: nounwind readnone uwtable
define internal fastcc i32 @"*fib_signed<Int32>:Int32"(i32 %n) #5 {
entry:
%0 = icmp slt i32 %n, 2
br i1 %0, label %exit, label %else
else: ; preds = %entry
%1 = add i32 %n, -1
%2 = tail call fastcc i32 @"*fib_signed<Int32>:Int32"(i32 %1)
%3 = add i32 %n, -2
%4 = tail call fastcc i32 @"*fib_signed<Int32>:Int32"(i32 %3)
%5 = add i32 %4, %2
ret i32 %5
exit: ; preds = %entry
ret i32 %n
}
; Function Attrs: nounwind readnone uwtable
define internal fastcc i64 @"*fib_signed<Int64>:Int64"(i64 %n) #5 {
entry:
%0 = icmp slt i64 %n, 2
br i1 %0, label %exit, label %else
else: ; preds = %entry
%1 = add i64 %n, -1
%2 = tail call fastcc i64 @"*fib_signed<Int64>:Int64"(i64 %1)
%3 = add i64 %n, -2
%4 = tail call fastcc i64 @"*fib_signed<Int64>:Int64"(i64 %3)
%5 = add i64 %4, %2
ret i64 %5
exit: ; preds = %entry
ret i64 %n
}
; (略)
しかし、fib32
もfib_32<Int32>
という関数名になっているのは不思議だ……