おおお腕時計のバンドがイカれてしまった。これの一個前に使ってたバンドが安物で、すぐ切れてしまったので、時計屋さんの、「やっぱり安物はすぐ駄目になるからねぇ」という言葉を信じて、やや(僕の感性で)高いと感じるやつにしたのに、一年ちょっとでイカれてしまいやがった。なんてこった。
もう二度と高級指向なんて信じないからな!安物買いの銭失いなんてありえない。安いものこそ正義、吉野家こそ最適解!ユニクロ以外は存在しない!!
4.0.0
あぶない、あぶない、あやうくスルーしてしまうところであった。いや、別にわざわざ触れなくてもいいような気もするんだけど。
「過去の経験から言って、多分ダメっぽいから次のバージョンまで待つよね」
みたいな通ぶった発言をしてる奴は人生の余裕が足りない!通ぶるなら、もっと、3.4.3とdiffとって観賞してほくそ笑むぐらいの変態ぶりを見せてほしいところである。いや、実際やってたらキモいと思うが。
まあ、それはいいとして、3.4.3までのGCCには-daとゆーオプションがあって、各パスごとのダンプを出力することができた。
これ見ると、「うひょー、30パス以上もあるのか…へぇー」って気分になれるかと思う。
で、4.0なんだが、これに加えて、-fdump-tree-allというオプションができた。4.0の目玉機能である、tree形式における最適化の各パスごとの出力を得られるオプションなのだが、こいつを実行してみると、なんと、69パス分(!!)の出力が得られるのだ。うひょー、RTLの最適化35パスと併せると100パス超えですよ。100パスて。
まあ、実際には、ちょろっと実行されるだけのようなパスもあるんだろうけど、とりあえず、4.0がこれまでのバージョンのGCCとは別物であるだろうということはおわかりいただけるかと思う。だからどうしたって話なんだが。
スタックマシンと最適化
さて、いつまでもコンパイラやらインタプリタやら書き捨てばっかりなのもつまらんな、と思って、a24zでは最適化でもかじってみようかと思ったのだ。が、そろそろ集中力が切れてきたのでやらないかもしれない。
で、実装するかしないかはまあいいとして、そこらへん関係で、色々スタックマシン(というか、JVM)のコードを吐くコンパイラの最適化について調べてみた。が、しかし、なんということか、最適化を行っているコンパイラは、ほとんど存在しないのである。唯一確認できたのは、KopiのコンパイラKJCだけであった。他のはちょろっと定数計算を行っているくらいであった。Luaコンパイラにいたっては、定数計算すら行っていなかった。
が、しかし、それは仕方無いのかもしれない。ここらへん調べていたころ、色々と考えていたのだが、スタックマシンでは中途半端な最適化は逆効果になることが多いからだ。
例えば、
// a, x, y はローカル変数。 a = (x*y) + (x*y)
こんな感じの式があったとして、こいつを単純にスタックマシンのコードに落とすと、
# load → ローカル変数を読み出してスタックの先頭に (2 byte) # store → スタックの先頭の値をローカル変数に (2 byte) # add → スタックの上ふたつの値を加算 (1 byte) # mul → スタックの上ふたつの値を乗算 (1 byte) 0: load x 2: load y 4: mul 5: load x 7: load y 9: mul 10: add 11: store a
こんな感じになると思う。で、ここで、見るからに無駄っぽい計算があるので、最適化を行ってみることにする。まず、最適化を行うには、三番地文という単純な形式に変換する。別に変換しなくてもいいんだけど、三番地文にすると処理が単純になるとかなので、ここらへんは定石というか、定跡というか。
t1 = x * y t2 = x * y a = t1 + t2
で、こーなると、x*yが共通部分式だとわかるので、こいつを消して、
t1 = x * y a = t1 + t1
こんな感じ、で、これをスタックマシンのコードに変換すると、
0: load x 2: load y 4: mul 5: store t1 7: load t1 9: load t1 11: add 12: store a
なんと、命令数は変わらないうえに、コードサイズは増えてしまうのである。乗算は一回減っているので、まあ、悪くはないかもしれないが、良い感じはしないだろう。また、上の例の場合だと、共通部分式があったから辛うじて同じぐらいの質のコードを生成しているが、普通はあまりそういう状況も無いので、逆に悪くなることが多いと予想されるかと思う。
そんな感じで、算術演算とスタックマシンというのは、普通の状態でそれなりに相性がいいので、中途半端なことをするといけないのである。
で、それだったら、普通に何の最適化もしないほうがいいんじゃないか、と、いった感じで、スタックマシン用の最適化コンパイラというのはあまり存在しないのだと思う。
ちなみに、あまり関係無いけど、KopiのKJCはここらへんも考慮したうえで最適化をしているようで、それなりに賢いコードを生成するようだ。現存するJavaコンパイラの中で、一番賢いのはこのKJCである、ということは、知っておいて損は無いと思う(Visual J++とかは知らんけど)。ローカル変数の節約とかもしてくれるし。ただ、開発止まってるっぽいのだけど。
まあ、とにかく、上のような事情があるのか無いのか、JIT最適化の事情だか知らないけど、javacとjikesが最適化しないのだ。SunとIBMが諦めてるんですよ。さらにいうならgcjも放置してるような問題ですよ。こんなの、やらないほうが普通だよ。一介のサラリーマンが触れるような問題じゃないんだ。
と、そんな感じで、どうでもいいか、と思ってたら、なんとVC++6.0の生成する浮動小数点387コード(これもスタックマシンね)が、見てるだけで失禁しそうになるほど美しく、無駄の無いコードだったので、やっぱり諦めたらいかん、と思ったわけですよ。もう、ほんと、あれはヤバい。あれより良いコードは手でも生成できる気がしない。色々なコード生成させてるだけで楽しめるし。
と、いうわけで、普段から「Microsoftはマーケティング上手いだけで技術力はアレだね」とか言ってる以上(いや、言ってないけど)Microsoftに負けるわけにはいかんので、スタックマシンにおけるコード生成について考えてみよう、という話なのですよ。
続く、としたいところですが、やる気が微妙なので続くかどうか微妙。
あと、GCCの387コードの生成が参考になるかな?と思って見てみたんだけど、GCCの吐いたコードは、計算するたびにfxchでスタックの先頭を入れかえて適当にごまかしているというちょっとアレなコードだったので、参考にならなかった。
が、しかし、パイプラインやらレジスタリネーミングやらの都合で、実は、387の場合はそっちのほうが効率がいいとか、悪いとか。
参考