コードをどのように解釈しても効率がほとんどありませんか? (理論)
-
26-09-2019 - |
質問
さて、最初に、私はここにどんな種類のflamewarやそのようなものを望んでいません。私のより大きな質問はより理論的であり、いくつかの例が含まれます。
だから、私が書いたように、私は解釈された言語がどのように効率的でないかを理解することができません。そして、そのモダン以来、私はJavaを模範とします。
JITコンパイラがなかった日々に戻りましょう。 Javaには、基本的にハードウェアである仮想マシンがあります。少なくとも仮想マシンから少なくともある程度の仕事を取得するためにbytecodeにコンパイルされたよりも、コードを書きます。それは問題ありません。しかし、RISC命令セットがハードウェアでどのように複雑になるかを考えると、ソフトウェアエミュレートハードウェアでそれを行う方法さえ考えられません。
仮想マシンを書いた経験はないので、それがどのように効率的なレベルでどのように行われたかはわかりませんが、適切なアクションよりも一致ADNのすべての命令をテストするよりも強化は何も考えられません。ご存知のように、次のようなもの if(instruction=="something") { (do it) } else if(instruction=="something_diffrent"){ (do it) }
等....
しかし、これはひどく遅くなければなりません。それでも、JITコンパイラーの前にJavaが遅い記事でさえ、彼らはまだそれほど遅くないと言っています。しかし、エミュレートするには、1つのバイトコード命令を実行するには、実際のHWの多くのクロックサイクルを要する必要があります。
それでも、プラットフォーム全体でさえJavaに基づいています。たとえば、Android。そして、Androidの最初のVerisonsにはJITコンパイラがありませんでした。彼らは解釈されました。しかし、Androidがひどく遅くなるべきではありませんか?それでもそうではありません。 AndroidライブラリからAPI関数を呼び出すと、マシンコードで書かれているため、効率的であるため、これは非常に役立ちます。
ただし、画像を表示するためだけにAPIを使用して、Sratchから独自のゲームエンジンを作成すると想像してください。多くの配列コピー操作を行う必要があります。これは、エミュレートすると非常に遅くなる多くの計算です。
そして今、私が約束したようにいくつかの例があります。私は主にMCUと仕事をしているので、Atmel AVR MCUのJVMを見つけました。 Thayは、8MHz MCUが毎秒20K Javaオプトコードを実行できると述べています。ただし、AVRは1つまたは2つのサイクルでほとんどの指示を行うことができるため、平均6000000命令を使用してください。これにより、JITコンパイラのないJVMがマシンコードが300倍遅いことがわかります。では、なぜJITコンパイラなしでJavaにとても人気になるのでしょうか?これはあまりにも悪いパフォーマンスの損失ではありませんか?理解できません。ありがとう。
解決
長い間バイトコードがありました。古いApple IIでは、USCD P-Systemが非常に人気があり、PascalをBYTEコードにまとめました。これは、2 MHzで実行される可能性のある8ビット6502で解釈されます。これらのプログラムはかなり速く実行されました。
バイトコードインタープリターは、通常、のチェーンではなくジャンプテーブルに基づいています if/then/else
ステートメント。 CまたはC ++では、これには switch
声明。基本的に、通訳者は処理コードの配列に相当し、バイトコード命令のオペコードを配列のインデックスとして使用します。
また、マシンの命令よりも高いレベルのバイトコードを使用することもできます。そのため、1つのバイトコード命令がいくつかの、時には多数のマシンコード命令に変換されます。特定の言語用に構築されたバイトコードは、その特定の言語の制御構造とデータ構造とのみ一致する必要があるため、これをかなり簡単に実行できます。これにより、解釈が頭上に伸び、通訳がより効率的になります。
解釈された言語は、コンパイルされた言語と比較すると、速度のペナルティがある可能性がありますが、これはしばしば重要ではありません。多くのプログラムは、人間の速度で入力と出力を処理し、それにより、無駄になる可能性のある膨大な量のパフォーマンスが残ります。ネットワークバウンドプログラムでさえ、必要以上のCPUパワーを利用できる可能性があります。取得できるすべてのCPU効率を使用できるプログラムがあり、明らかな理由で解釈された言語では書かれていない傾向があります。
そして、もちろん、違いを生むかもしれないし、そうでないかもしれない、何らかの非効率性のために何を得るかという問題があります。解釈された言語の実装は、コンパイルされた実装よりもポートが簡単になる傾向があり、実際のバイトコードはしばしばポータブルです。高レベルの機能を言語に簡単に配置する方が簡単です。これにより、コンパイルステップをはるかに短くすることができます。つまり、実行ははるかに速く開始できます。何か問題が発生した場合、より良い診断が可能になる場合があります。
他のヒント
しかし、その後、Androidがひどく遅くなるべきではありませんか?
「ひどく遅い」を定義します。電話です。 2番目の数字をダイヤルする前に、「最初の数字をダイヤル」処理する必要があります。
インタラクティブなアプリケーションでは、制限要因は常に人間の反応時間です。 100時間遅く、ユーザーよりも速くなる可能性があります。
したがって、質問に答えるために、はい、通訳者は遅いですが、特にハードウェアがより速くなっているため、通常は十分に速いです。
Javaが導入されたとき、それはWebアプレット言語として販売されたことを忘れないでください(置き換えて、今ではJavaScript--------解釈)。 JITコンピレーションの後にのみ、サーバーで人気を博しました。
バイトコード通訳者は、ジャンプテーブルを使用することにより、if()sのラインよりも速くなります。
void (*jmp_tbl)[256] = ...; /* array of function pointers */
byte op = *program_counter++;
jmp_tbl[op]();
この質問にアプローチするには、2つの異なる方法があります。
(i)「なぜ遅いコードを実行しても大丈夫なのか」
ジェームズがすでに上記で述べたように、実行の速度があなたが興味を持っているすべてではないことがあります。解釈モードで実行されている多くのアプリの場合、「十分に速く」可能性があります。あなたが書いているコードがどのように使用されるかを考慮する必要があります。
(ii)「なぜ解釈コードは無意味です」
通訳を実装する方法はたくさんあります。あなたの質問では、あなたは最もナイーブなアプローチについて話します:基本的には大きなスイッチで、読み取りに合わせて各JVM命令を解釈します。
ただし、最適化できます。たとえば、単一のJVM命令を見る代わりに、それらのシーケンスを調べて、より効率的な解釈を利用できるパターンを探すことができます。 SunのJVMは、実際には、通訳自体でこれらの最適化の一部を行います。以前の仕事では、男が通訳をそのように最適化するのに少し時間がかかり、Java Bytecodeが変更後に著しく速く実行されていたと解釈しました。
しかし、JITコンパイラを含む現代のJVMでは、インタープリターはJITが仕事をするまで足がかりの石です。そのため、人々は通訳を最適化するのにそれほど多くの時間を費やすことはありません。
12 MHzは、8ビットマイクロプロセッサであるAttinyです。つまり、(たとえば)ネイティブ「Add」命令は2つの8ビット番号しか追加できないことを意味します。9ビットの結果を得ることができます。JVMは基本的に仮想32ビットプロセッサです。つまり、その追加命令は2つの32-を追加します。ビット番号を一緒にして、33ビットの結果を生成します。
そのため、命令率を比較する場合、命令率の4:1の低下は絶対的な最小値として期待されるはずです。現実には、4つの8ビットの追加(キャリー付き)で32ビットの追加を簡単にシミュレートするのは簡単ですが、そのようなものはまったくスケーリングしません。たとえば、Atmel自身のものによると アプリノート, 、32ビットの結果を生成する16x16の乗算は、〜218クロックサイクルで実行されます。同じアプリノートは、255サイクルで実行される16/16ビット分割(8ビット結果の生成)を示しています。
これらのスケールを直線的に仮定すると、32ビットバージョンの乗算が〜425-450クロックサイクル、および分割〜510サイクルを摂取することが期待できます。現実には、おそらく少し頭上を期待する必要があります。これにより、速度がさらに低下します。これらの推定に少なくとも10%を追加すると、おそらくより現実的になります。
結論:リンゴをリンゴと比較すると、話している速度の違いの多くがまったく現実ではないことが明らかになります(または、JVMオーバーヘッドとはとにかく)。