Top > Programmingとか > VB / VB.NET > p-codeコンパイルとネイティブコードコンパイル

1998年03月22日

05 どっちのコンパイルを使えばいいんだろぅ

日付を管理していなかったので、正確な記述日時がわかりません。
ので、サイト「猿頁」開設日としました。御了承ください。

以上、BASICの発展過程と併せて2種類のコンパイル形式が存在するいきさつをお話してきました。

で、つまるところ、

どっちのコンパイル形式を使えばいいんだ?

って話になると思ぅんですが。

世間様一般には、「ネイティブコードコンパイルのほうがいいぞぅ」といぅことになっていますが、私としては

ふつーは p-code のほうがいいぞぅ

と思っています。

まーたそんなヘソ曲がりなことを言い出したよおまえさんは、って古典落語みたいなツッ込みを入れる前に、まぁちょいと聞いておくんなさいよ。

ネイティブ EXE は、p-codeEXE よりも動作が速いといぅことになっています。けど。実はこの「動作が速い」ってのは、限りなく絵に描いた餅に近いホントです。

確かネイティブコードコンパイル機能を搭載した VB5.0 の発表当時、Microsoft は「p-codeEXE と比較して最高で 8 倍、平均 3 倍の動作速度を実現」(だったかな)って言っていたはずです。

けど、この「平均 3 倍」ってのは意味がないですね。どこのどんなプログラムを集めてきて平均したのか知りませんけど、VB を使っている個々の開発者にとって唯一の関心事は「オレが今作っているプログラムはどのくらい速くなるんだ?」ってことでしかないんですから。平均値ではなくてね。

で、VB のネイティブ EXE が自分の中でマシン語として動作を実現しているのは、演算子だけを使った計算と一部 API 関数のみです。関数を使った演算や、VB の仕様の中でソフィスティケートされた API 機能については、ランタイム内のサブルーチンをコールします。ランタイム内サブルーチンの動作速度は、p-codeEXE からコールされてもネイティブ EXE からコールされても変わるわけはないんですから、この部分についての動作速度は同じですね。

また、標準添付されている ActiveX コントロールや、サードパーティから提供されている別売りのコントロールに関わる動作速度も p-codeEXE と変わりません。この場合のボトルネック(動作速度が遅くなる一番の原因)はコントロール側、及びコントロールとの I/O にあるので、自作プログラム側だけがいくらマシン語になったってスピードは変わりません。ねぇ。

しかし、VB 製プログラムの用途のかなりの部分をフロントエンドまたはスタンドアローン、つまり使用者が直製キーボードやマウスで操作するのが主目的のプログラムが占めています。で、そーいぅプログラムはネイティブコンパイルしたってさほど速くなりません

逆にネイティブEXEの短所としては、

  • プログラムサイズがでかいために配布がしにくくなる

  • コンパイルにもぅれつに時間がかかる

  • VBの開発環境(IDE)での実行と動作が異なる
と、開発に時間がよけいにかかりやすくなっています。特に「IDE での実行と動作が異なる」ってのは、割とみなさんハマるところです。イベントの実行順とか変わるんですよ、けっこぅ。

本来ネイティブ EXE での配布を前提にするなら、動作テスト・デバッグ作業もネイティブ EXE で行わなきゃいけません。IDE でインタプリタ実行しながらのテストで完動したからって、それがネイティブ EXE での動作保証の根拠にはなりませんもん。

でも、これって修正をかけるたびにネイティブコードコンパイルをかけ直さなきゃなりませんし、IDE 環境でのデバッグもできません。本来、ネイティブ EXE にするんなら、デバッグは Visual Studio などのシンボリックデバッガ環境とかで行うのがスジなんですもん。

デバッグ作業は効率よく IDE 内で実行しながら行い、ちょっとでも速くなることを期待してネイティブコードコンパイル。で、配布開始したり客先に納品したりしてから「ちゃんと動かない」ってクレームをいただいて「な、なぜなんでしょぅねぇ」などと情けなげな返答をしたりして。あぁいやだ。

ので、さほど動作速度が変わらないタイプのプログラムなんであれば、p-codeEXE をものすごくおすすめします。ベンチマークとかで厳密に測定しなくても、体感的に差違が感じられないのであればネイティブEXEにするメリットはまったくありませんよ。

絶対にネイティブEXEをおすすめしちゃうのは、VC++ などとのマルチランゲージでシステム開発するとき。VC++ で DLL 作って VB から Call するよぅなパターンですね。こんな時はシンボリックデバッガで VC++ も VB もまとめてデバッグしたいわけですから。VB 製プログラムをネイティブ EXE にしといて、VC++←→VB 間をシームレスにソース追っかけていけるといぅ、涙が出るくらいありがたい環境になりました。

04 VBもネイティブコードコンパイルを実装した。…けど。

日付を管理していなかったので、正確な記述日時がわかりません。
ので、サイト「猿頁」開設日としました。御了承ください。

せっかくここまで述べちゃったので、もー少し Visual C++ のお話。

DOS 版 C の時代は、できあがった実行可能ファイルは EXE (あるいは COM …今の COM/DCOM ではなくて)単体だってのが基本でした。でかいシステムは EXE 分割してコールしたり、単一 EXE 内でロードできる単位にモジュールを作ってスワップしながら動作するよぅなコンパイルオプションでしのいだりしてました。数値演算ライブラリとかの別売りライブラリ(サブルーチン集ですね)も基本的にはコンパイル→リンクの過程でEXEの内部に取り込んでました。(ファンクションコールを拡張して、割り込みの形でルーチンを呼び出すタイプのライブラリもありましたけれども)。

ので、Visual C++ がお目見えしたときに、やはり単体 EXE(そのファイルひとつだけで完全に動作するよぅな)を期待しちゃったんですね、皆さん。

…でも、気軽に単体 EXE を作るには、やはり Windows とのやり取りめんどくさすぎ。ってことで、Windows とのやり取りの部分をサブルーチンとして共通化しちゃえ。で、Windows に特化した(てゅうか、そのサブルーチンを活用するのを前提とした)記述形式をオリジナル形式で新たに作っちゃえ。と、「MFC」というサブルーチンが標準構成として提供されてしまいました。

この MFC を実現するサブルーチンは、MFC42.DLL とかその他の DLL 形式で提供されています。VC++ が 4→5→6 とバージョンアップしていく過程で、またこの DLL がバージョン間で不整合起こしたりして頭痛の種になっていくわけなんですが、まぁそのへんのいきさつはまた機会がありましたら。

ここで理解しておいていただきたいのは、そーいぅ状況の中で、VC++ としては

  1. システムディレクトリの中にあるMFC42.DLL他をほかのプログラムと共有するようなEXE
    (プログラムサイズは割と小さい/DLLバージョン間不整合に悩まされやすい)

  2. MFC42.DLL他を自分の中に取り込んだEXE
    (プログラムサイズが取り込んだDLL分、異様にでかくなる)

  3. 一切MFCのお世話にならず、Windowsとのやり取りを全部自力で実装したEXE
    (プログラムサイズは一番小さい/一番高速/すごく作成がめんどくさい)
ってゅう3タイプを選択できるようになっちゃっいました、ってことです。

さて。お待たせしました。話をVBに戻します。

VB も、ユーザからの強い要望があったんだかなんなんだかで、Ver5.0 よりネイティブコードコンパイル機能がよぅやくお目見えしました。この話が発売前に伝わってきたときに皆さんが期待したのは、「あのクソでかいランタイムなしの、単体 EXE が VB でも作れるよぅになるのではないか?」でした。

…が。それって、VC++ で言えば 3. を期待した、ってことなんです。VC++ でもすごく作成がめんどくさい 3.。それを、「作成の簡単さ」がウリのVBで?

実際に VB5 を入手して、BooksOnline をガツガツと読み漁って。…やられたぁ。ランタイムを VC++ のMFC 回りに見立てた 1. 方式だっ。ランタイムの中の各ルーチンを、ネイティブ EXE の中からそのつど呼び出す方式だぁ。結局、従来どおりランタイムの配布は必須なんだぁ。

これはもちろん、VB6 でも変わりませんでした。ランタイム必須のネイティブ EXE。

現在、ワカっている VB ユーザの間では、いつか 2. 形式の EXE-ランタイムを内部に取り込んだ EXE が可能になる日がくるのではないか、といぅのがせつないと言えるほどの希望になっています。

ですが、私の個人的な予測としては、まず無理。あるいは、無駄。

バージョンを重ねるごとに、なんかよくわからない機能が拡張されていってしまい、今やランタイムの内部はちょっとやそっとでは切り分けられないほど複雑怪奇になっているはず。なので、もし今後 2. 形式が実現可能形式になるとしたら、ランタイムをファイル全部丸ごと EXE の中に埋め込んじゃうのではないかと。現状、VC++ の 2. 形式も MFC 回り丸ごと取り込みみたいですしね。
ってことは、結局ランタイムのファイルサイズ分肥大してしまうだろぅなぁってことで、配布上のメリットはあんまりないっす。

「インストーラの作成が不要」ってメリットがありますね。でもこれ、ActiveX コントロールを使うのであれば結局インストーラの作成が必要ですし、標準コントロールのみで望みの機能を一通り作れるだけのスキルを持つ人ってやっぱ少数派でしょうし。

SP 間不整合の問題の回避ってメリットもありますが、これ、ことはランタイムだけの問題ではなく、ActiveX コントロールや IE のバージョンとも絡んでくるので、ランタイムさえ内包したらすっきり解決ってわけにはいかないでしょうしね。

逆に、VB 製 EXE がそれぞれランタイム相当のファイルサイズ分肥大してたら、HDD を圧迫すること請け合い。わたし的には、なんかそっちの方が「やだ」って感じですねぇ。

03 ネイティブコードコンパイル

日付を管理していなかったので、正確な記述日時がわかりません。
ので、サイト「猿頁」開設日としました。御了承ください。


これで、ふつーにインタプリタ実行するよりは「解釈」にかかる時間を節約したファイルができあがりました。当時の MS-DOS 用 BASIC では、これでなんとか DISK-BASIC 版とどっこい程度の動作速度を実現していたと記憶しています。
もっともインタプリタ(これは「開発環境」といぅ意味での)\48,000.-、コンパイラ\98,000.-、別途ランタイム(もちろん再配布不可)\12.800.-/1台あたり、くらいの値段だったはずなので(うろ覚え^_^;)、フリーウェア文化なんかなかなか育つ環境ではありませんでしたけれども。

さて。この p-code コンパイル、やっぱちょっと邪道のよぅな気がしませんか?コンパイルって「翻訳」って意味ですし、ソース→ p-code に「翻訳」するんですからウソをついているわけではないんですけれども。

でも、パソコンで「コンパイル」するんなら、やっぱソース→マシン語を期待するのが人の世の常。で、テキストエディタやなんやが普及していくにしたがって、パワーのある人は C に乗り換えていったりして。で、BASIC 以外の言語を勉強するのがめんどくさい人は Microsoft BASIC や Quick BASIC (これも Microsoft 製だったりして)に流れていったりして。

この、ソース→マシン語への翻訳を行ってくれるコンパイル作業のことを、p-code に対してネイティブコードコンパイル(ネイティブコード=自然な言語、ってここではマシン語のことを指します)と呼ぶようになったのは、VB が言い出しっぺじゃありませんでしたっけ。当時は、「ホントのコンパイル」とか「マシン語へのコンパイル」とか呼んで区別してました、私(^_^;)。
ちなみに私は、この件で BASIC に割とうんざりしていて C に流れたんですけど、Microsoft Cじゃなくて Lattice C だったりして。当時個人的には富士通派で、学校では日立の 2020 使ってて。就職のときに NEC 系を選んで、友人には「転び PC」と差別受けたりして。したっけ NEC が自社ブランドで販売しているのがMicrosoft Cしかなくて、さらにコロんでみたりして。…流されてますなぁ。

時代はがばっと進み、OS が MS-DOS→OS/2→Windows3.0/3.0A/3.1/95 と進んでいく中で、「割とかんたんに開発できる言語/環境」ってやつも Visual Basic2.0→4.0 と進んでいきました(日本の場合ですよ。本家アメリカでは Windows も 3.11 ワークグループとかってバージョンもあったはずですし、VB だって16ビット時代の名作と評価が高い 3.0 もありましたしね。聞くところによると VB1.0 for MS-DOS ってのもあったらしいんですが、これ、日本語版は出たのかしら)。

対して、初期のネイティブコードを作り出せる開発環境は Microsoft C7.0+SDK (当時は SDK だけで単体売りされる有料商品)しかなくって、ややこしすぎて嫌んなった人たちがもぅ一度 BASIC…いゃいゃ、Visual Basic(この大文字と小文字の区別は意外と大事。古い人は、すべて大文字表記の「BASIC」は DOS 版か DISK BASIC 版、大文字小文字混じりの「Basic」は Visual Basic ってこだわってたりしますので)へ舞い戻ってきたりしていたわけですね。

戻ってはきたものの。確かにそこそこ動くものは作れるものの。やはり DOS 時代に C で、Microsoft/Quick BASIC で、なじんだネイティブコンパイル環境がほしい!って願望はやはり強かったわけですね。
で、さらに新しい開発環境にチャレンジできるだけの精神力を持った人たちは、Microsoft C+SDK の次世代を担う新しい開発環境、Visual C++ へと突撃していったわけです。

02 インタプリタってなんだ?

日付を管理していなかったので、正確な記述日時がわかりません。
ので、サイト「猿頁」開設日としました。御了承ください。

さて、このへんで区切りを入れましょうか。

ここまでの説明で、BASIC の場合、インタプリタってのが OS のよぅな言語のよぅな開発環境のよぅな実行環境のよぅな曖昧もこっとしたものとして呼ばれてきちゃったといぅ状態をご理解いただけましたでしょぅか?

その上で。狭義のインタプリタといぅのは、動作のときのスタイルを指して使う言葉なんですね。ので、現在の VB でも共通していますが、ソースコードを書き込んだりセーブしたりといぅ機能のことはちょっと忘れてください。

じゃ、どんなふうに動作するのがインタプリタなんだ?といぅ話にこれから持っていきます。

BASIC の開発環境の中から私たちの作ったプログラムを実行すると、その中の最初の1行をデータとして読み込みます。で、そこに書かれている BASIC 文法にのっとって書かれた文字列データを解釈して、どう動作すればいいかを解読します。で、解読したとおりに実行します。実行が終わったら、その次の1行を読み込んで、また解釈して実行します。
といぅふうに、延々と読込み→解釈→実行を繰り返すんですね。

これが「インタプリタ実行形式」って動作です。逐次実行形式とも呼ぶんですが、これをインタプリタの和名だと言っちゃっていいかどぅかについてはちょっと自信のないところ。

この「解釈」の部分なんですが、要は、たとえば文字列データに「Print」と書いてあったら、それが機能番号何番なのかを予約語表と照らし合わせて、その後ろに書かれているパラメータを引数として、その機能番号何番のサブルーチンをコールする、といぅことをやるわけです。100回「Print」命令が書かれていたら、100回解釈します。あたりまえですが。この解釈の繰り返しに、結構時間がとられるんですね。

この「解釈」にかかる時間を省略できないか?どぅせ文字列で書かれた命令と機能番号は1対1で対応するんだから、私たちが作ったもともとのプログラムファイルの中でも、機能番号として保存しておけばいいじゃん。って発想が出てきます。

で、このひととおり解釈し終わった状態のプログラムファイルを、p-code 形式(Packed Codeの略です。日本語では「中間コード」と呼ばれたりもします)と呼ぶわけです。 あぁ、よぅやく p-code までたどり着いたぞ。

てことで、「p-code コンパイル」ってのは、とりあえず

プログラムソースを機能番号にあらかじめ置き換えて、
ニセ実行形式ファイルを生成する

ことだと思ってください。

ホントはいろいろとそーでなかったりするケースもあるんですが、ややこしくなりすぎてかえってよく把握できなくなったりするので、そのくらいの理解でいいです。

実際に p-code コンパイルされたデータは、実行可能ファイル「EXE」として保存されます。実際はこれ、データだけでは起動しよぅがないので、ファイルの最初に「ランタイムを呼び出して実行する」といぅマシン語が埋め込まれています。ので、この p-codeEXE を実行すると、まずランタイムを起動し、制御をそっちに移し、あらためて自分自身はデータファイルとして読んでもらうというちょっとややこしい動作をします。

01 BASICはインタプリタだった

日付を管理していなかったので、正確な記述日時がわかりません。
ので、サイト「猿頁」開設日としました。御了承ください。

むかーしの話から始めなきゃならないんですけどね。てゅうか、むしろ始めたい。

BASIC はもともと大型コンピュータしかなかった時代に、大学生のプログラミング言語学習用に、FORTRAN を基にして開発された言語だったそぅです。これを開発されたばっかりのマイコン(今のパソコンの前身ですね。現在家電などの中にチップの形で埋め込まれている「マイコン」とはちょっと違う意味合いだと考えてください)に ROM の形で搭載できるよう、ビル・ゲイツがものすごくコンパクトにまとめてハードウェアメーカー各社に提供したのがマイコン BASIC のそもそもでした。たぶんこれ、ホントにビル・ゲイツが自分で開発した唯一のプログラムなのではないかしら。

このときの BASIC は、OS の機能をも兼ね備えていました。てゅうか、冗長な OS を別に搭載するだけのメモリが、高価すぎてとても当時のマイコンには搭載できなかったんですね。で、簡単なラインエディタや、カセット(当時マイコン用の FD なんかまだありませんでしたもん)へのロード/セーブ機能までを内包した、言語なんだか開発環境なんだか OS なんだかよくわからない、電源さえ入れれば自動的に起動して「Ready OK」とか表示されるといぅ、なかなか摩訶不思議な状態だったんです。

電源さえ入れれば BASIC が起動しちゃうわけですので、プログラムファイルってのは基本的にはソースのまんまでさしつかえなかったわけです。当時コンパイラはあったかなぁ…あったかもしれませんけど、私の記憶にはありません。

さて、時が少しずれてドクター中松の大発明、フレキシブルディスク(確か「フロッピーディスク」ってのはIBMかどっかの商標名だったよぅな記憶が…)の恩恵の波がマイコンにもやってきました。BASIC も ROM BASIC → DISK BASIC が主流となり、やがて OS 部分が分離・独立して提供されるようになってきたのは1980年代に入ってまもなくだったよぅな。個人的には1983年に、「これからはマイコンも OS の時代だ」「BASIC で事足りているのになぜわざわざマシンスピードを落とすような中間ソフトを介入させるのか」でけんかした覚えが。

この時期は、わやわやといろんな OS が群雄割拠しておりました。8ビットマシン(PC88 や FM8/7、MZ-80…わぁなつかしい)では CP/M の独壇場だったきらいがありますが、16ビットマシン(PC-98、FM16/11、MZ-2000 とかでしたっけ)では CP/M86・OS-9・MS-DOS がシェアを争い、生き残ったのが MS-DOS。

MS-DOS については、新しい16ビットマシン用の OS を IBM が探しているという話を聞いたゲイツが、売り先に困っていた OS を抱えていたソフトハウスから権利もろとも買い上げて、1週間で IBM-PC 用に改造して見事納品した、って逸話がありますね。

世の16ビットマシン(そろそろ「パソコン」と呼ばれるよぅになってきました。最初に「Personal Computer」って言い出したのは NEC だったかなぁ…ビジネス用としても使えますってあたりを強調したいがための呼称でしたね)の標準 OS は MS-DOS となりましたけれども、その上でプログラムを自作するための言語/開発環境としては、やはり BASIC がトップシェアとなりました。マイコンに携わっている人間自体は変わらないわけですから、慣れた開発環境のほうがいいに決まってますもんね。

もともと BASIC って遅かったんですけれども(当時のシューティングゲーム…っていってもスペースインベーダーみたいなもんですが…はオールマシン語/16進バイナリ手入力、なんてのがあたりまえでしたし)、OS を介しちゃったもんだからますます遅さに輪がかかっちゃって。もともとマイコン用 OS ってのは低レベル入出力ルーチンの共有(≒開発効率の向上)と、実行可能ファイルの Load & Run を実現するためのローダーとしての位置付けで普及していったものですから、やっぱワンクッション置く分だけのろいのはしょーがないわけです。

で、よぅやくここで「BASIC コンパイラ」が陽の目を見ます。私がその存在を記憶しているのはMS-DOS 版 BASIC コンパイラからですので、それ以前に存在していたとしてもあまり普及はしていなかったのではないでしょぅか。DOS 版コンパイラで当時¥98,000.-(うろ覚え)もしていましたしね。もちろん開発環境とは別売りですぜ。

コンパイラに対する従来の言語/開発環境/動作環境としての BASIC は、インタプリタと呼ばれます。いぇ、最初っからインタプリタだったんですけれどもね。コンパイラの存在が知られるまでは、別にインタプリタかどぅかなんてどーでもよかったので、みなさん「BASIC」としか呼んでいませんでしたから。