
| プログラム実行 |
|---|
| 一般的な概念 |
| コードの種類 |
| コンパイル戦略 |
| 注目すべき実行時間 |
| 注目すべきコンパイラとツールチェーン |
|
コンピューティングにおいて、インタープリタとは、ソースコードを機械語にコンパイルせずに実行するソフトウェアです。インタープリタ型ランタイム環境は、実行前にソースコードを変換する必要があるCPUネイティブ実行コードを処理する環境とは異なります。インタープリタは、ソースコードをバイトコードなどの中間形式に変換する場合があります。ハイブリッド環境では、.NETやJavaのように、バイトコードを直接解釈するのではなく、ジャストインタイムコンパイルによってバイトコードを機械語に変換する場合があります。
インタプリタが広く普及する以前は、コンピュータプログラムの実行は、ソースコードを機械語に変換・コンパイルするコンパイラに依存することが多かった。LispやBASICの初期の実行環境は、ソースコードを直接解析できた。その後、 Perl、Raku、Python、MATLAB、Rubyなどの言語向けの実行環境が開発され、実行前にソースコードを中間形式に変換することで実行時のパフォーマンスを向上させた。
インタプリタで実行されるコードは、互換性のあるインタプリタを持つあらゆるプラットフォームで実行できます。プラットフォームごとに実行ファイルをビルドする必要はなく、同じコードをあらゆるプラットフォームに配布できます。各プログラミング言語は通常、特定の実行環境に関連付けられていますが、言語は異なる環境で使用できます。ALGOL 、Fortran、COBOL、C、C++など、従来はコンパイルを伴う言語向けにインタプリタが構築されています。
コンピュータが普及し始めた頃は、当時のハードウェアではインタープリタと解釈されたコードの両方をサポートできず、また当時の典型的なバッチ環境では解釈の利点が制限されていたため、インタープリタよりもコンパイラの方が一般的に使用されていました。[ 1 ]
インタプリタは、1952年という早い時期から、当時のコンピュータの限界(プログラム記憶領域の不足、浮動小数点数のネイティブサポートの欠如など)の中でプログラミングを容易にするために使用されていました。また、インタプリタは低水準機械語間の翻訳にも使用され、開発中のマシン向けにコードを記述し、既存のコンピュータでテストすることができました。[ 2 ]最初のインタプリタ型高水準言語はLispでした。Lispは、Steve RussellによってIBM 704コンピュータ上で初めて実装されました。RussellはJohn McCarthyの論文「Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I」を読んで、Lispのeval関数をマシンコードで実装できることに(McCarthyを驚かせた)気づきました。[ 3 ]その結果、Lispプログラムを実行できる、より正確には「Lisp式を評価」できるLispインタプリタが完成しました。
編集インタープリタの開発は、対話型コンピューティングのニーズに影響を受けました。1960年代には、タイムシェアリングシステムの導入により、複数のユーザーが同時にコンピュータにアクセスできるようになり、編集インタープリタはリアルタイムでのコード管理と変更に不可欠なものとなりました。最初の編集インタープリタは、メインフレームコンピュータ向けに開発されたと考えられており、プログラムをリアルタイムで作成および変更するために使用されました。編集インタープリタの最も初期の例の一つは、1960年代後半にPDP-1コンピュータ向けに開発されたEDT(Editor and Debugger for the TECO)システムです。EDTは、コマンドとマクロを組み合わせてプログラムを編集およびデバッグすることを可能にし、現代のテキストエディタや対話型開発環境への道を開きました。
通訳の主な用途は次のとおりです。
解釈オーバーヘッドとは、ネイティブ(コンパイル済み)コードではなくインタープリタ経由でコードを実行する際の実行時コストです。インタープリタはネイティブコード内の同等の機能を実行するために複数のマシンコード命令を実行するため、解釈処理は遅くなります。特に、インタープリタでは変数へのアクセスが遅くなります。これは、識別子を記憶域にマッピングする処理がコンパイル時ではなく実行時に繰り返し実行されるためです。 [ 4 ]しかし、開発期間の短縮(編集・ビルド・実行サイクルの短縮などによる)は、実行速度の高速化の価値を上回る可能性があります。特に、編集・ビルド・実行サイクルが頻繁に発生するプロトタイプ作成やテストでは、その傾向が顕著です。[ 4 ] [ 5 ]
インタプリタは、高速な実行性能といった目標を達成するために、ソースコードからプログラムの中間表現(IR)を生成することがあります。コンパイラもIRを生成することがありますが、コンパイラは後で実行するための機械語を生成するのに対し、インタプリタはプログラムを実行する準備をします。これらの異なる目的により、IRの設計は異なります。多くのBASICインタプリタは、キーワードを1バイトのトークンに置き換え、ジャンプテーブルで命令を見つけるために使用します。[ 4 ] PBASICインタプリタなどの一部のインタプリタは、バイト指向ではなくビット指向のプログラムメモリ構造を使用することで、さらに高度なプログラム圧縮を実現しています。この場合、コマンドトークンはおそらく5ビットを占有し、名目上は「16ビット」の定数は3、6、10、または18ビットを必要とする可変長コードに格納され、アドレスオペランドには「ビットオフセット」が含まれます。多くのBASICインタプリタは、独自のトークン化された内部表現を保存し、読み出すことができます。
インタプリタを用いた開発速度とコンパイラを用いた実行速度の間には、様々な妥協点があります。一部のシステム(一部のLispなど)では、インタプリタで実行されたコードとコンパイルされたコードが相互に呼び出し、変数を共有できます。つまり、あるルーチンをインタプリタでテストおよびデバッグした後、それをコンパイルすることで、他のルーチンを開発している間に実行速度の向上を実現できるのです。
解釈とコンパイルの初期段階は似ているため、インタープリタはコンパイラと同じ字句解析器とパーサーを使用して、結果として得られる抽象構文ツリーを解釈する場合があります。
C++で書かれた式インタープリター。
stdをインポートします。std :: runtime_errorを使用します。std :: unique_ptrを使用します。std :: variantを使用します。// 抽象構文ツリーのデータ型enumクラスKind : char { VAR 、CONST 、SUM 、DIFF 、MULT 、DIV 、PLUS 、MINUS 、NOT };// 前方宣言クラスNode ;クラス変数{ public : int *メモリ; };クラス定数{ public : int値; };クラスUnaryOperation { public : unique_ptr < Node > right ; };クラスBinaryOperation { public : unique_ptr < Node > left ; unique_ptr < Node > right ; };Expression = variant < Variable , Constant , BinaryOperation , UnaryOperation >を使用します。クラスNode { public : Kind kind ;式e ; };// インタープリター手順[[ nodiscard ]] int executeIntExpression ( const Node & n ) { int leftValue ; int rightValue ; switch ( n -> kind ) { case Kind :: VAR : return std :: get < Variable > ( n . e ). memory ; case Kind :: CONST : return std :: get < Constant > ( n . e ). value ; case Kind :: SUM : case Kind :: DIFF : case Kind :: MULT : case Kind :: DIV : const BinaryOperation & bin = std :: get < BinaryOperation > ( n . e ); leftValue = executeIntExpression ( bin . left . get ()); rightValue = executeIntExpression ( bin . right . get ()); switch ( n . kind ) { case Kind :: SUM : return leftValue + rightValue ; case Kind :: DIFF : return leftValue - rightValue ; case Kind :: MULT : return leftValue * rightValue ; case Kind :: DIV : if ( rightValue == 0 ) { throw runtime_error ( "ゼロ除算" ); } return leftValue / rightValue; } case Kind :: PLUS : case Kind :: MINUS : case Kind :: NOT : const UnaryOperation & un = std :: get < UnaryOperation > ( n . e ); rightValue = executeIntExpression ( un . right . get ()); switch ( n . kind ) { case Kind :: PLUS : return + rightValue ; case Kind :: MINUS : return - rightValue ; case Kind :: NOT : return ! rightValue ; } default : std :: unreachable (); } }ジャストインタイム(JIT)コンパイルとは、中間形式(バイトコードなど)を実行時にネイティブコードに変換するプロセスです。これによりネイティブコードが実行されるため、インタープリタを使用する際の実行時コストを回避しつつ、インタープリタ開発の原動力となった利点の一部を維持する手法です。