インタプリタ

出典: フリー百科事典『ウィキペディア(Wikipedia)』

これはこのページの過去の版です。みそがい (会話 | 投稿記録) による 2016年3月11日 (金) 10:14個人設定で未設定ならUTC)時点の版 (Okipow (会話) による ID:58906830 の版を取り消し)であり、現在の版とは大きく異なる場合があります。

インタプリタ: interpreter)とは、プログラミング言語で書かれたソースコードないし中間表現にある命令列を逐次解釈しながら実行英語版するプログラムのこと。

概要

インタプリタは、およそ次のいずれかの動作をするプログラムである。

  1. ソースコードを直接実行する。
  2. ソースコードを何らかの効率的な中間表現に変換しながら実行する。
  3. システムの一部であるコンパイラが生成し出力した、コンパイル済みの中間表現を実行する[1]。ソースプログラムはマシンに依存しない中間的なコードに事前にコンパイルされ、実行時にリンクされ、インタプリタで実行される。

BrainfuckLazy KマイクロコンピュータTiny BASIC は1の例である。PerlPythonRubyProlog は2の例、Java.NET Framework などは3の例である。

もともとは、プログラミング言語処理系の実装には、インタプリタとコンパイラの2つがある、とされてきた。インタプリタは実行を行うが、コンパイラは実行を行わない、という差がある。

もともとは、インタプリタはプログラム文を1文ずつ機械語に変換していく(単純な)方式が一般的であった。

この時代のインタプリタの長所と欠点については、およそ次のような解説がされることが一般的であった。

  • (長所)プログラムを作成している途中でも、とりあえず書かれた箇所まで実行させることができ、プログラマの期待通りの動作をしている場合も、期待通りの動作をしていない場合も、早期にそれを確認・発見し、そして修正後すばやく実行、再確認できる。
  • (短所)実行速度が遅い。(ループ(=繰り返し)の箇所などでは)1度構文解釈した文でも、毎回(あらためて、最初から)解釈と実行を行うので、コンパイラ方式に比べて実行速度が遅くなる。(ループを全く含まないような、全ての命令が一回だけ解釈され、一回だけ実行されるような(ある意味、特殊な)プログラムであれば、解釈+実行のトータルの時間は、インタプリタでもコンパイラでもさほど差は生じない。だが、実用的なプログラムは一般的に、多数のループを含んでおり、そうしたプログラムではインタプリタのほうが実行を完了するまでの時間が多くかかり、特に、ループ回数が多ければ多いほど(古典的な、単純な)インタプリタの相対的な遅さは顕著になってくる。→#インタプリタの長所と短所

その後、そうした欠点を解消すべく、(1990年代ころになって)毎回毎回、(高級言語から)機械語に変換するのではなく、中間言語に変換することで高速化をはかるインタプリタなどが作りだされた。

改良の結果、古典的な意味での「インタープリタ」と「コンパイラ」の双方の性質を備えたようなインタープリタが登場し、複雑化してきている。

(近年の)インタプリタがおこなう、(旧来の)コンパイラが行っているような変換のひとつとしては、高速化などを目的とした、実行時コンパイラによる動的コンパイルを挙げることができる。

[2] [3]

歴史

世界初の、インタプリタが実装された高水準言語は LISP だと言われている。最初の LISP 処理系はスティーブ・ラッセルIBM 704 上に実装した。ラッセルはマッカーシーの論文を読み、マッカーシーも驚いたことに LISPeval 関数を機械語で実装してみせた[4]。これにより LISP プログラムを実行できる、より正確には「LISP の式を評価」できる LISP インタプリタが生まれた。

インタプリタの長所と短所

初期のインタプリタと、最近のインタプリタでは、長所や短所は異なる。短所をなくすべく(減らすべく)、しくみを変えてきた歴史があるためである。

開発時に修正作業が容易

プログラム開発中、プログラマは頻繁にソースコードに手を加える。コンパイラの場合、ソースコードを変更するたびにコンパイルし、リンクして実行ファイルを完成させないと、そのプログラムを実行できない。プログラムが大きくなると、ビルドの完了を待っている時間が長くなる。一方、インタプリタではソースコードをそのまま実行するか中間表現に変換するだけなので、ほとんど待つ必要がなく、修正がうまくいったかどうかのテストをより素早く確認できる。

配布する上でのメリットとデメリット

コンパイラは一般にソースコードを特定のプロセッサアーキテクチャの機械語命令列に変換するので、生成されるプログラムは特定のアーキテクチャのプロセッサでしか動かない。この変換は開発者の環境で一度だけ行われ、そのバイナリが配布され、ユーザー側では変換を行う必要がない。クロスコンパイラを使えば、他のプロセッサアーキテクチャ向けのバイナリを生成することができる。

インタプリタの場合、ソースコードを配布できる。その変換はユーザー側で行う必要があるが、特定のアーキテクチャに依存しないプログラムの配布が可能である。ただし、その場合ユーザーのマシン上に適当なインタプリタが実装されていなければならない。インタプリタとソースコードを同時に提供する必要があるなら、単に実行ファイルを配布した場合よりインストールが全体として複雑化することもある。

インタプリタ用コードは人間が容易に読むことができるため、著作権保護の観点から問題があるとする見方もある。しかし、そのための様々な暗号化やオブファスケーション英語版のシステムも存在する。バイトコードのような中間コードを配布する場合、ある程度はオブファスケーションと同様の効果があるが、バイトコードを逆コンパイラあるいは逆アセンブラでデコードすることも可能である。抗逆コンパイル性のあるオブファスケーションを考慮したバイトコードとする設計もありうる。

短所: 実行速度の遅さ

インタプリタ最大の短所は、コンパイラよりも実行時の性能が低いことである。この性能差は様々で、時には桁違いとなることもある。プログラムの実行時間はコンパイラよりもインタプリタの方が長いが、コンパイル時間と実行時間を合計すればインタプリタでの実行時間よりも長くなることがある。プロトタイピングテストにおいては、この差が重要となる。

コンパイラではプログラム内のの解析を実行前に1回だけ行うが、単純な実装のインタプリタではそれを文ごとに実行時に毎回行うため、実行性能が低くなる。単純な実装のインタプリタでは変数にアクセスする際も識別子とメモリ上の位置のマッピングを確認しなければならず、しかもそれを実行中に何度も行わなければならないので、遅くなるのである。

単純な実装のインタプリタ方式が速度が遅くなる最大の原因は、一命令ごとに switch 文を実行することにある。現代のCPUパイプライン方式を採用しており、命令の先読みが可能でないと、実行速度が著しく低下する。switch 文の場所が命令の先読みが不可能であるため、単純な実装のインタプリタ方式は遅くなる。単純な実装のインタプリタ方式で実装された処理系を高速化(最適化)するための、最初にとられるステップが switch 文を廃止し、コンパイラ方式に切り替えることである。それゆえ、実装不可能というわけではないが、効果が薄いため、インタプリタ方式で実装する場合は、一般には、制御フロー解析静的単一代入などを使った、複数の命令を超えての最適化が実装されないことが多い。

インタプリタによる開発の速さとコンパイラによる実行の速さの間で、様々な妥協案が考案されてきた。一部の LISP 処理系などでは、インタプリタのコードとコンパイルされたコードが相互に呼び出しあうことができ、変数も共有できる。そのため、あるルーチンをインタプリタで評価しデバッグした後、先行してコンパイルして実行性能を高めつつ、他のルーチンを開発し続けることができる。多くのインタプリタはソースコードをそのまま実行するわけではなく、よりコンパクトな内部形式に変換している。多くの BASIC インタプリタは予約語を1バイトトークンに置換し、それをジャンプテーブルのインデックスとして使用する。PBASIC など一部のインタプリタでは、バイト単位ではなくビット単位でプログラムの短縮を行っており、例えばコマンドを5ビットで表し、一般に16ビットで表される定数をその数値の大きさに対応して可変長(3、6、10、18ビットなど)で表し、アドレスオペランドとして「ビットオフセット」を用意している。多くの BASIC インタプリタは独自にトークン化された内部表現を保存し、読み込むことができる。

インタプリタがコンパイラと同様の字句解析構文解析を行い、その結果生成された抽象構文木を解釈することもある。

バリエーション

バイトコードインタプリタ

ソースコードを実行可能な形にするには、まず、ソースコードを構文木に変換する必要がある。構文木のまま、インタプリタ型の処理系で実行する処理系もあるが、構文木をさらに、中間コード(バイトコードなど)などの中間表現に変換してから実行する物もある。中間コードをバイトコードと呼んでいる処理系ではそのインタプリタをバイトコードインタプリタと呼ぶ。Java.NET Framework のように、中間コードの仕様を公開しファイルに書き出すものもあるし、仕様は公開せず処理系内部だけで使用するものもある。動的コンパイルを使っているインタプリタは、内部で実機の機械語に変換し実行する。

インタプリタとコンパイラの間には様々な中間的実装が存在し、それぞれにプログラム実行前に行われる解析の度合いが異なる。例えば Emacs Lispバイトコードにコンパイルされ、LISP のソースを高度に圧縮し最適化した表現にしているが、それは機械語コードではない(したがって特定のプラットフォームに依存しない)。この「コンパイル」されたコードを解釈するのがバイトコードインタプリタである(それ自体は C で書かれている)。この場合のコンパイルされたコードは仮想機械の機械語コードであり、仮想機械はハードウェアで実装されておらず、バイトコードインタプリタとして実装されている。同様の手法は Open Firmware システムで使われている Forth コードでも使われている。ソース言語は「Fコード」(バイトコードの一種)にコンパイルされ、それを仮想機械が解釈実行する。他にPコードマシンなどがある。

コントロール・テーブル英語版はコンパイラを通さなくとも生成でき、バイトコードインタプリタと同様の方法でカスタマイズされたインタプリタでの適切なアルゴリズム的制御構造を記述できる。

抽象構文木インタプリタ

インタプリタとコンパイラの中間的手法の1つとして、ソースコードを最適化された抽象構文木 (AST) に変換し、その木構造にしたがってプログラムを実行するか、実行時コンパイラでの機械語コード生成に使用する方法がある[5]。この方式では各文を1回だけ構文解析する必要がある。バイトコードに比べると、ASTではプログラムの全体的構造や文と文の関係を保持でき(それらはバイトコードでは失われる)、圧縮するとさらにコンパクトな表現になる[6]。そのため、実行時コンパイラにとってはバイトコードよりもASTの方が優れた中間表現だとして提案されてきた。また、実行時の解析もより優れたものにできる。

しかし、ASTはバイトコードよりも冗長であるため、インタプリタとしてはオーバーヘッドが大きくなるという問題がある[7]CRuby の場合は、1.8までは構文木インタプリタであったが、1.9では(開発中には YARV と呼ばれていた)バイトコードインタプリタに入れ替えられ、性能が向上した。

実行時コンパイル

インタプリタとコンパイラの境界をさらにぼやけさせる方式として、中間表現を実行時コンパイラ (JIT) でコンパイルし、実行時にネイティブの機械語にコンパイルする技法がある。これはネイティブなコードの実行効率を実現する代わり、ASTやバイトコードを最初にコンパイルする際に起動時間やメモリ使用量が増大するという欠点がある。これを補う技法として適応的最適化英語版があり、インタプリタが実行中のプログラムを性能解析して最も頻繁に実行される部分をネイティブのコードにコンパイルする。これらの技法は1980年代の Smalltalk などの言語で使われ始めた[8]

実行時コンパイルは近年多くの言語処理系で採用されており、Java.NET Framework、最近のJavaScript の実装でも JIT が採用されている。

トランスレータ方式

他のインタプリタ言語に変換して、ターゲット言語のインタプリタ上で実行する方式。例えば CoffeeScriptJavaScript に変換されて、JavaScript インタプリタ上で実行される。

応用

  • インタプリタは、コマンドライン用言語やグルー言語でよく使われている。
  • 自己書き換えコードはインタプリタでは容易に実装できる。これは LISP人工知能研究がインタプリタの起源であったこととも関係している。
  • 仮想機械を使って、あるアーキテクチャを別のアーキテクチャ上で実行させる仮想化は、基本的にインタプリタである。
  • サンドボックス: インタプリタまたは仮想機械はソースコードの命令を全て実際に実行することを強制されない。特にセキュリティを脅かすような処理の実行は拒否できる。

デバッグ、教育用インタプリタ

通常C言語はコンパイラで処理されるが、デバッグ目的および教育目的のインタプリタ型のC言語の処理系もある。MS-DOS時代に、いくつかの製品が提供されていた。C-Terpなどがその様な製品の例である。C/C++のインタプリタはほかにCINTChがある。

パンチカードのインタプリタ

パンチカードシステムにおいて、パンチカードを読み込んで、その内容を人間が読める形式(文字)でパンチカード上に印字する機械をインタプリタと呼ぶ。例えば、IBM 550英語版 Numeric Interpreter (1930年) や IBM 557英語版 Alphabetic Interpreter (1954年) がある。

インタプリタ型の処理系が一般的なプログラミング言語

インタプリタとコンパイラ方式が併用の物

「共通言語ランタイム」のバイナリー・コードにコンパイルされるもの

Erlang VM」(BEAM)のバイナリー・コードにコンパイルされるもの

Java仮想機械」のバイナリー・コードにコンパイルされるもの

JavaScript に変換されるもの

Lua VM」のバイナリー・コードにコンパイルされるもの

「Pコードマシン」のバイナリー・コードにコンパイルされるもの

Parrot」のバイナリー・コードにコンパイルされるもの

Smalltalk VM」のバイナリー・コードにコンパイルされるもの

脚注

  1. ^ この意味では、CPUは機械語インタプリタであると見ることができる[要出典]
  2. ^ 現在では、「インタープリタ / コンパイラ」という区分に関しては状況が変わっており、[誰?]に言わせると『{だが、それらは必ずしも相互排他的に2つに分類できるわけではない。なぜなら多くのインタプリタ方式の処理系は、コンパイラが行っているような変換も内部で行っているからだ。[要出典]」とも言われ、『「インタプリタ言語」あるいは「コンパイラ言語」といった呼称も見掛けることがあるが、これらは単にその言語の規範的実装がインタプリタかコンパイラかを示しているに過ぎない(実際、詳しく調べれば、実験的な程度の実装まで含めれば両方ともあるということも多い)。[要出典]』という見解も出てくることになる。高水準言語は基本的に抽象であり、(理想的には)特定の実装からは独立している。しかし、動的プログラミング言語のようにインタプリタでの実装が向いている方向性の言語、あるいはその逆もあるということは確かである。
  3. ^ (比較的若い研究者である)伊藤は(インタープリタの発展史には言及せず)個人的なウェブページで次のように書きこんだ。「インタプリタはプログラムを逐次機械語に変換して実行する、という説明を仄聞するが、正しくない」と(伊藤 潔、interpreter and virtual machine : インタプリタと仮想機械、2007年4月22日)。(現在では)そのような動作は動的コンパイルであり、それを行うインタプリタもあるが、全てのインタプリタがそのような動作をおこなうわけではないので、インタプリタ全部の説明とは言えない。また同様に「インタプリタは中間言語に変換してそれを実行する」という説明も、インタプリタ全部の説明とはいえない。結局、さまざまなタイプのインタープリタが存在している(存在してきた歴史がある)ので、これに関しては、一般論が困難な状態になってしまっているのである。
  4. ^ ポール・グレアムの『ハッカーと画家』(原著「Hackers & Painters、185ページ)によれば、マッカーシーは「ラッセルは『ねえ、この eval をプログラムしようか』と言った。…私は『ほう、ほう。君は理論と実際を混同している。この eval は読み物として書いたもので、実際に動かすために書いたものじゃない』と答えた。しかし彼はそれをやってのけた。つまり彼は私の論文にある evalIBM 704 の機械語にコンパイルして、バグを修正し、それを LISP インタプリタだと宣伝したし、実際それはそのとおりだった。だからその時点で LISP は今日のような形態を本質的に備えていた」と述べたという。
  5. ^ AST intermediate representationsLambda the Ultimate forum
  6. ^ A Tree-Based Alternative to Java Byte-Codes — トーマス・キスラー、マイケル・フランズ
  7. ^ Annoucing SquirelFish
  8. ^ L. ドイチュ、A. シフマン、Efficient implementation of the Smalltalk-80 systemProceedings of 11th POPL symposium、1984年

関連項目

外部リンク

この記事は2008年11月1日以前にFree On-line Dictionary of Computingから取得した項目の資料を元に、GFDL バージョン1.3以降の「RELICENSING」(再ライセンス) 条件に基づいて組み込まれている。