include/linkスイッチによる抽象化

 仮想関数(抽象化)は、異なる2つの環境に、ひとつのソースコードで対応するための数ある対応策のひとつとしてしばしば利用されます。この方法は、ソースコードの字面は非常に綺麗になりますが、全般的な処理が重くなるため、プログラマにとって "理性が受け入れても、生理的に受け入れがたい" 選択といえます。

 問題は、現在のコンパイラは仮想関数をジャンプテーブルを用いた間接ジャンプに翻訳するため、 CPU が持っているなけなしの分岐予測もほぼ効果を発揮せず、確実にパイプラインストールを発生させてしまう点にあります。仮にコンパイル時に仮想関数として通し、ランタイム時に確定させるとなると、将来 JIT によるランタイム最適化が実現されるまで待たなければいけないでしょう。

 仮想関数は非常にうまい方法ですが、少なくとも起動前に確定していることは畳み込みたいところです。例えば、PS3Xbox360の機種の差を埋めるマルチプラットフォーム対応ミドルウェアがあったとします。両機種の差異を仮想関数で抽象化して埋める方法がまず考えられますが、実際に完成したソフトの使われ方を考えますと、同じプログラムがPS3Xbox360を切り替えることはありえません(物理的に無理)。

 つまりアプリがビルドされる時点で、どちらのコードを使うかが確定されています。今回のテーマは、 "プログラマが同じコードを使って、PS3Xbox360でそれぞれコンパイルできればよい" ということになりますので、抽象化ではなく、リンクするライブラリやインクルードするファイルをスイッチする方法でも対応することができます。この方法は環境の差異のために仮想関数を使いませんので、オーバーライドを許さない関数のすべてのアドレスをリンク時に確定することができます。またインライン展開も非常に効率的に行われるでしょう。

 こういったやり方は、マルチプラットフォーム対応では典型的な対策のひとつで、少し前のPCゲームでは、各CPUへのインラインアセンブリコードやスクリプトJITを切り替えるビルドなどで使われていました。コンパイラのバージョンの差異をつぶしたり、デバッグバージョンのライブラリをリンクする[y/n]などでもお馴染の方法です。

 そして恐らくスクリプトプログラミングでも有効です。スクリプトプログラミングは "実行時解釈" の性質がフォーカスされがちですが、 "実行時リンク" という要素も持っています。

 たとえば PHP4/PHP5 環境の差異を取り除いてくれる関数やマクロを作ることを考えてみます。このプラットフォーム条件は、プログラムの稼動中に切り替わるものではありません。したがって同一関数名が定義されているファイルを読み込む際に、環境の条件にあわせてロードするファイルを切り替えることでも対応ができます。これは C/C++ などで用いているインクルードやリンクのスイッチとほぼ同じ意味を持ち、各関数内でそれぞれ環境を判断してスイッチするよりパフォーマンス的にうまく働きます。

 ただ、 C/C++ ならこの方法はスタンダードのひとつと言えますが、スクリプトプログラミングの世界にはまた別のレギュレーションがありますので、そこが議論を呼んでしまうかもしれません。