ocamloptを読んだ
ocamloptを読んでみた。そして色々と改造してみたりして分かった事をメモしておきます。
- 整数値、アドレスのボックス化は2倍して1足すという方法で行っている。
- 例えばprint_int 100と書くと実際にはprint_int 201という呼び出しになる。
- 四則演算などは、この変換を行った状態で実行される。例えばたし算の場合は、足した後に1を引くという計算になる。
- これはRubyとかでやっているのと同じ方法
- どんなに小さい関数でもlet rec ...と定義するとインライン化されない。
- let recと定義された関数が再帰関数であるかどうかのチェックはしていない。
- [| ... |] と定義された配列と、Array.createで生成された配列は実体が異なる。
- 特に[| 1.0; 2.0; ..|]のような配列は、メモリ上に直接浮動小数点数が配置されるが、Array.create 10 1.0の様に書くと、ポインタを介するアクセスになる模様。
- 高度な最適化といえるものはほとんど行われていない。
- OCamlの例外は軽い。
- 例外というと重いというイメージがあるけれど、OCamlの場合にはtry .. withという限定された構文でしか使えないので軽く実装できる。
- forループからbreakで抜けるくらいの気軽さで使える。
ocamloptのコードについて。
- アーキテクチャ毎の定義ファイルの設計が美しいと思った。
- スケジューラや命令選択等の一般的な定義を記述したスーパークラスを作成しておき、各アーキテクチャ毎のコードでそれを継承して、サブクラスを作っている。
- サブクラス内でスーパークラス内のメソッドをオーバーライドするなどして、必要な部分だけ再定義するという書き方になっている。
- OCamlのオブジェクト指向については良く知らないのだけれど、inheritを使っているのをはじめてみた。
- ところでi386向けのスケジューリング定義ファイルの中身は「何もしない」になっていた。i386の場合にはハードウェア任せにしてしまった方が十分速いのだとか。
- ちなみにGHCは一つのファイルに#if i386_TARGET_ARCHみたいにプリプロセッサディレクティブを使ってごちゃっと書いている。
- 入れ子になったパターンマッチをじゃんじゃん使っている。
- こういうコードを書いてもそこそこ速く動いてしまうというのがうらやましい。
- ボックス化された整数くらいCboxed_...みたいに表して欲しい。正直読みにくい。
Cconst_int n -> Cconst_int(n asr 1) | Cop(Caddi, [Cop(Clsl, [c; Cconst_int 1]); Cconst_int 1]) -> c | Cop(Cor, [Cop(Casr, [c; Cconst_int n]); Cconst_int 1]) when n > 0 && n < size_int * 8 -> Cop(Casr, [c; Cconst_int (n+1)]) | Cop(Cor, [Cop(Clsr, [c; Cconst_int n]); Cconst_int 1]) when n > 0 && n < size_int * 8 -> Cop(Clsr, [c; Cconst_int (n+1)]) | Cop(Cor, [c; Cconst_int 1]) -> Cop(Casr, [c; Cconst_int 1]) | c -> Cop(Casr, [c; Cconst_int 1])
- 副作用を使いまくっている。
- 例えばC--の内部表現は命令がポインタで連結された形で表現されている。スケジューリング等はこのポインタの張替えとして実装されている。
以前GHCを読んだ訳ですが、言語が違うと(人が違うと?)ここまでコードが変わるんだなぁという。
OCamlは関数型言語であることを生かしてガンガン最適化をするというような言語ではなく、言語設計からして高速化を意図して作られているのだろうなぁ。