長い間、私はC++がJavaScriptよりも高速であると考えていました。しかし、今日、2つの言語の浮動小数点計算の速度を比較するベンチマークスクリプトを作成しました。結果は驚くべきものです。
JavaScriptはC++のほぼ4倍の速さです!
I5-430Mラップトップで両方の言語に同じ仕事をさせて、a = a + b
100,000,000回。 C++は約410ミリ秒かかりますが、JavaScriptは約120ミリ秒しかかかりません。
この場合、なぜJavaScriptが非常に高速に実行されるのか、私にはまったく分かりません。誰もそれを説明できますか?
JavaScriptに使用したコードは次のとおりです(Node.jsで実行)。
(function() {
var a = 3.1415926, b = 2.718;
var i, j, d1, d2;
for(j=0; j<10; j++) {
d1 = new Date();
for(i=0; i<100000000; i++) {
a = a + b;
}
d2 = new Date();
console.log("Time Cost:" + (d2.getTime() - d1.getTime()) + "ms");
}
console.log("a = " + a);
})();
C++(g ++でコンパイル)のコードは次のとおりです。
#include <stdio.h>
#include <ctime>
int main() {
double a = 3.1415926, b = 2.718;
int i, j;
clock_t start, end;
for(j=0; j<10; j++) {
start = clock();
for(i=0; i<100000000; i++) {
a = a + b;
}
end = clock();
printf("Time Cost: %dms\n", (end - start) * 1000 / CLOCKS_PER_SEC);
}
printf("a = %lf\n", a);
return 0;
}
Linuxシステム(少なくともこの状況ではPOSIXに準拠しています)を使用している場合、悪いニュースがあるかもしれません。 clock()
呼び出しは、プログラムによって消費され、CLOCKS_PER_SEC
(1,000,000
)でスケーリングされたクロックティック数を返します。
つまり、このようなシステムで()であれば、マイクロ秒Cの場合、ミリ秒JavaScriptの場合( JSオンラインドキュメント に従って)。したがって、JSは4倍速くなるのではなく、C++は実際には250倍速くなります。
CLOCKS_PER_SECOND
が100万以外のものであるシステムにいる場合は、システムで次のプログラムを実行して、同じ値でスケーリングされているかどうかを確認できます。
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define MILLION * 1000000
static void commaOut (int n, char c) {
if (n < 1000) {
printf ("%d%c", n, c);
return;
}
commaOut (n / 1000, ',');
printf ("%03d%c", n % 1000, c);
}
int main (int argc, char *argv[]) {
int i;
system("date");
clock_t start = clock();
clock_t end = start;
while (end - start < 30 MILLION) {
for (i = 10 MILLION; i > 0; i--) {};
end = clock();
}
system("date");
commaOut (end - start, '\n');
return 0;
}
私のボックスの出力は次のとおりです。
Tuesday 17 November 11:53:01 AWST 2015
Tuesday 17 November 11:53:31 AWST 2015
30,001,946
スケーリング係数が100万であることを示しています。そのプログラムを実行するか、CLOCKS_PER_SEC
を調査し、それがnot100万のスケーリング係数である場合、他のことを調べる必要があります。
最初のステップは、コンパイラーによってコードが実際に最適化されていることを確認することです。これは、たとえば、gcc
に-O2
または-O3
を設定することを意味します。
最適化されていないコードを使用するシステムでは、次のように表示されます。
Time Cost: 320ms
Time Cost: 300ms
Time Cost: 300ms
Time Cost: 300ms
Time Cost: 300ms
Time Cost: 300ms
Time Cost: 300ms
Time Cost: 300ms
Time Cost: 300ms
Time Cost: 300ms
a = 2717999973.760710
-O2
では3倍速くなりますが、答えはわずかに異なりますが、百万分の1程度しかありません。
Time Cost: 140ms
Time Cost: 110ms
Time Cost: 100ms
Time Cost: 100ms
Time Cost: 100ms
Time Cost: 100ms
Time Cost: 100ms
Time Cost: 100ms
Time Cost: 100ms
Time Cost: 100ms
a = 2718000003.159864
これにより、JavaScriptは昔のように解釈される獣ではなく、各トークンが表示されるたびに解釈されるので、2つの状況は互いに同等に戻ります。
最新のJavaScriptエンジン(V8、Rhinoなど)は、コードを中間形式(またはマシン言語)にコンパイルできるため、Cなどのコンパイル言語とほぼ同等のパフォーマンスが得られる場合があります。
しかし、正直に言うと、JavaScriptまたはC++を選択する傾向はありません。その強みの領域に合わせて選択します。ブラウザ内には多くのCコンパイラが浮かんでおらず、JavaScriptで書かれた多くのオペレーティングシステムや組み込みアプリに気付いていません。
最適化を有効にして簡単なテストを行ったところ、古代のAMD 64 X2プロセッサーで約150ミリ秒、かなり最近のIntel i7プロセッサーで約90ミリ秒の結果が得られました。
次に、C++を使用する理由の1つを少し説明しました。これを得るために、ループの4つの反復を展開しました。
#include <stdio.h>
#include <ctime>
int main() {
double a = 3.1415926, b = 2.718;
double c = 0.0, d=0.0, e=0.0;
int i, j;
clock_t start, end;
for(j=0; j<10; j++) {
start = clock();
for(i=0; i<100000000; i+=4) {
a += b;
c += b;
d += b;
e += b;
}
a += c + d + e;
end = clock();
printf("Time Cost: %fms\n", (1000.0 * (end - start))/CLOCKS_PER_SEC);
}
printf("a = %lf\n", a);
return 0;
}
これにより、AMD上でC++コードを約44msで実行できます(Intel上でこのバージョンを実行するのを忘れていました)。次に、コンパイラの自動ベクトライザー(VC++での-Qpar)をオンにしました。これにより、時間はさらに少し短縮され、AMDでは約40ミリ秒、Intelでは30ミリ秒になりました。
結論:C++を使用する場合、コンパイラーの使用方法を学ぶ必要があります。本当に良い結果を得たいなら、おそらくより良いコードを書く方法を学びたいと思うでしょう。
追加する必要があります:Javascriptでループを展開した状態でバージョンをテストしようとしませんでした。そうすることで、JSでも同様の(または少なくともいくつかの)速度の改善が得られる可能性があります。個人的には、JavascriptをC++と比較するよりも、コードを高速にする方がはるかに興味深いと思います。
このようなコードを高速に実行するには、ループを展開します(少なくともC++では)。
並列計算の主題が生じたので、OpenMPを使用して別のバージョンを追加すると思いました。その間、コードを少し整理したので、何が起こっているのかを追跡できました。また、内側のループを実行するたびに時間ではなく全体の時間を表示するように、タイミングコードを少し変更しました。結果のコードは次のようになりました。
#include <stdio.h>
#include <ctime>
int main() {
double total = 0.0;
double inc = 2.718;
int i, j;
clock_t start, end;
start = clock();
#pragma omp parallel for reduction(+:total) firstprivate(inc)
for(j=0; j<10; j++) {
double a=0.0, b=0.0, c=0.0, d=0.0;
for(i=0; i<100000000; i+=4) {
a += inc;
b += inc;
c += inc;
d += inc;
}
total += a + b + c + d;
}
end = clock();
printf("Time Cost: %fms\n", (1000.0 * (end - start))/CLOCKS_PER_SEC);
printf("a = %lf\n", total);
return 0;
}
ここでの主な追加は、次の(明らかにやや難解な)行です。
#pragma omp parallel for reduction(+:total) firstprivate(inc)
これは、各スレッドごとにinc
の個別のコピーを使用し、並列セクションの後にtotal
の個々の値を加算して、複数のスレッドで外部ループを実行するようコンパイラーに指示します。
結果は、おそらくあなたが期待するものについてです。コンパイラの-openmp
フラグを使用してOpenMPを有効にしない場合、報告される時間は以前の個々の実行の約10倍(AMDで409 ms、Intelで323 MS)です。 OpenMPをオンにすると、AMDでは217ミリ秒、Intelでは100ミリ秒になります。
そのため、Intelでは、元のバージョンは外部ループの1回の反復に90ミリ秒かかりました。このバージョンでは、外側のループの10回の繰り返しすべてでわずかに長くなります(100ミリ秒)-速度が約9:1向上します。より多くのコアを搭載したマシンでは、さらに改善が期待できます(OpenMPは通常、使用可能なすべてのコアを自動的に利用しますが、必要に応じてスレッド数を手動で調整できます)。
これは二極化されたトピックなので、以下をご覧ください。
https://benchmarksgame-team.pages.debian.net/benchmarksgame/
あらゆる種類の言語のベンチマーク。
Javascript V8などは、例のように単純なループに対して確かに良い仕事をしており、おそらく非常によく似たマシンコードを生成しています。ほとんどの「ユーザーに近い」アプリケーションでは、確実にJavscriptの方が適していますが、より複雑なアルゴリズム/アプリケーションでは、メモリの浪費と避けられないパフォーマンスヒット(および制御の欠如)が何度もあります。