まず、ほとんどのJVMにはコンパイラーが含まれているため、「解釈されたバイトコード」は実際には非常にまれです(少なくともベンチマークコードでは、実際のコードでは非常にまれではありません。通常、コードは、 )。
第二に、関与するベンチマークのかなりの数が非常に偏っているように見えます(意図または無能かどうか、私は本当に言うことはできません)。たとえば、数年前、私はあなたが投稿したリンクの1つからリンクされたソースコードのいくつかを見ました。このようなコードがありました:
init0 = (int*)calloc(max_x,sizeof(int));
init1 = (int*)calloc(max_x,sizeof(int));
init2 = (int*)calloc(max_x,sizeof(int));
for (x=0; x<max_x; x++) {
init2[x] = 0;
init1[x] = 0;
init0[x] = 0;
}
calloc
すでにゼロ化されているメモリを提供するため、for
ループをゼロ化して再び使用すると明らかに無駄になります。これに続いて(メモリが機能する場合)、メモリを他のデータで埋めることにより(ゼロ化されていることに依存しない)、ゼロ化はすべて不要でした。上記のコードを単純なものmalloc
(普通の人が最初から使用していたように)に置き換えると、C ++バージョンの速度がJavaバージョンに勝るのに十分なほど向上しました(メモリが提供される場合、かなり広いマージンで)。
(別の例として)methcall
最後のリンクのブログエントリで使用されているベンチマークを検討してください。名前(および物事がどのように見えるか)にもかかわらず、このC ++バージョンはメソッド呼び出しのオーバーヘッドについてはあまり測定していません。重要であることが判明したコードの部分は、トグルクラスにあります。
class Toggle {
public:
Toggle(bool start_state) : state(start_state) { }
virtual ~Toggle() { }
bool value() {
return(state);
}
virtual Toggle& activate() {
state = !state;
return(*this);
}
bool state;
};
重要な部分は判明したstate = !state;
。コードを変更してint
aの代わりに状態をエンコードするとどうなるか考えてみてくださいbool
:
class Toggle {
enum names{ bfalse = -1, btrue = 1};
const static names values[2];
int state;
public:
Toggle(bool start_state) : state(values[start_state])
{ }
virtual ~Toggle() { }
bool value() { return state==btrue; }
virtual Toggle& activate() {
state = -state;
return(*this);
}
};
この小さな変更により、全体的な速度が約5:1のマージンで向上します。ベンチマークはメソッド呼び出し時間を測定することを目的としていましたが、実際には、測定したほとんどの時間はint
との間で変換する時間でしたbool
。オリジナルによって示された非効率性は不幸であることは確かに同意します-しかし、実際のコードではほとんど発生しないようであり、発生した場合/発生した場合に修正できる容易さを考えると、考えるのに苦労しますそれは多くの意味として。
誰かが関連するベンチマークを再実行することを決定した場合、Javaバージョンにもほぼ同等の些細な変更が加えられることを追加する必要があります(または少なくとも一度は作成されます-テストを再実行していません)最近のJVMがまだ確認しています)Javaバージョンもかなり大幅に改善されています。Javaバージョンには、次のようなNthToggle :: activate()があります。
public Toggle activate() {
this.counter += 1;
if (this.counter >= this.count_max) {
this.state = !this.state;
this.counter = 0;
}
return(this);
}
this.state
直接操作する代わりにベース関数を呼び出すようにこれを変更すると、大幅に速度が向上します(ただし、変更されたC ++バージョンに対応するには十分ではありません)。
そのため、最終的に、解釈されたバイトコードとこれまでに見られた最悪のベンチマーク(私が見た)のいくつかについての誤った仮定になります。どちらも意味のある結果をもたらしていない。
私自身の経験では、経験豊富なプログラマーが最適化に同等の注意を払っていると、C ++はJavaよりも頻繁に勝ちますが、(少なくともこれら2つの間では)プログラマーやデザインほど大きな違いはありません。引用されているベンチマークは、彼らがベンチマークと称している言語についてよりも、著者の(能力)/(不)正直さについて多くを語っています。
[編集:上記の1か所で暗示されているが、おそらくそうあるべきだと直接述べられていないので、引用する結果は、その時点で最新のC ++およびJava実装を使用して5年前にテストしたときに得られた結果です。現在の実装でテストを再実行していません。しかし、一見すると、コードが修正されていないことが示されているため、変更されるのは、コンパイラーがコードの問題を隠蔽することだけです。
我々は、Javaの例を無視する場合は、しかし、あると解釈コードは(困難とやや変わったが)コンパイルされたコードよりも高速に実行するために実際に可能。
これが起こる通常の方法は、解釈されるコードがマシンコードよりもはるかにコンパクトであること、またはコードキャッシュよりも大きなデータキャッシュを持つCPUで実行されていることです。
このような場合、小さなインタープリター(たとえば、Forth実装の内部インタープリター)は、コードキャッシュに完全に収まる場合があり、それが解釈するプログラムは、データキャッシュに完全に収まる場合があります。キャッシュは通常、メインメモリよりも少なくとも10倍速く、多くの場合ははるかに速くなります(100倍になることは特に珍しいことではありません)。
そのため、キャッシュがメインメモリよりもN倍速く、各バイトコードを実装するのにNより少ないマシンコード命令が必要な場合、バイトコードが勝つはずです(単純化していますが、一般的な考え方はまだすべきだと思います)明らかである)。