トップ回答者
64bit化による処理速度の影響

質問
-
お世話になります。
Windows 7 Pro Visual Studio2008でC++、C#のアプリケーションを開発しています。
処理速度の向上を期待してアプリケーションの64bit化を行っていたのですが、私のアプリでは、64bit化によって起動速度は1秒ほど遅くなり、画像処理速度は約2倍に向上し、USB通信によるデータの転送やファイル保存処理は速度が変わりませんでした。
いろいろ検索してみたのですが、処理速度が向上あるいは劣化した報告は多数あるのですが、どういう理由で速度に影響を与えているのかを記述している記事になかなか巡りあえずここで質問させてもらいました。
例えば以下の記事で
・longの演算が速くなる
とありますが、これは1クロックで処理できるデータ量が32→64で倍になったという解釈で良いのでしょうか。
その他にも速度が速くなるなら速くなる理由、遅くなるなら遅くなる理由が知りたいと思っています。
どうかご教授の程よろしくお願いします。
回答
-
・longの演算が速くなる
とありますが、これは1クロックで処理できるデータ量が32→64で倍になったという解釈で良いのでしょうか。
違います。32bitモードには64bitの演算命令がありません。そのため32bitモードでの64bit演算は、筆算の要領で32bit演算を組み合わせての演算をします。一方64bitモードには64bitの演算命令があるため、1回の演算で結果が出せます。
というのが基本ですが、32bitモードでもSSE等の64bit演算があったりもして、常に基本通りというわけではありません。それに対してすでに指摘がありますが、メモリアクセスが32bitから64bitと2倍に膨れ上がるので遅くなることがあります。
またこれらはあくまでCPU内部の話であり、USBが64bit化されたり高速化するわけではありません。当然ですが、マウスカーソルが目にもとまらぬ速さで動いたり、ブラインドタッチで超高速なキー入力が実現できるわけでもありません。
それで、せれさんのプログラムは多数の64bit演算を行っていて、その演算時間が支配的なのでしょうか? そうでないのなら速くなるわけがありません。
- 回答としてマーク せれ 2011年9月9日 7:54
-
画像に対して2次元フーリエ変換をしています。ここの処理が約2倍速くなっています。ですが64bit演算を意識してコードを記述したわけではないです。
そういうことでしたか。32bit CPUは時代とともに命令が順次追加されていて使用するCPUごとに使える命令がバラバラになっています。先のコメントにも「SSE」を挙げましたが、その部分に当たります。で、64bit CPUではその辺りが一旦リセットされ、必ずSSE命令が存在することになっています。(最近はまたバラバラになりつつありますが…w)そのため、たぶん.NET Frameworkも64bitではSSE命令を積極的に使います。
フーリエ変換をされているとのことですが、こういった計算はもともとSSE向きで最適化すれば速くなります。そうでなくてもSSE自身、従来の命令よりも高速ですので、特に意識していなくても2倍速くなったのかもしれません。
- 回答としてマーク せれ 2011年9月9日 7:54
-
すでにご存知かもしれませんが。。。
遅くなった処理部分の理由はわかりませんが、速くなった処理部分に関しては、WOW64 による
Emulation Layer の介在の有無があると思います。
(それが速くなった根本的な要因ではなく、あくまでも速くなった一つの要因。。。と言うことだともいますが。)確か、64 ビット プラットフォーム上で 32 ビット アプリケーションを動作させる場合、必ず WOW64 による
Emulation Layer が介在するはずです。
つまり、32 ビット アプリケーションからのリクエストは全て WOW64 によって 64 ビットのネイティブに変換されて、
最終的に 64 ビットとして処理されるはずで、64 ビット アプリケーションでは WOW64 による Emulation Layer
が介在しないので、そのオーバヘッド部分は確実に速くなるはずだと思います。とりあえず "WOW64" でグーグル先生に聞いてみたら、下記のサイトを教えてくれました。
x64で本格化する64ビットWindowsの時代(3)
http://itpro.nikkeibp.co.jp/article/COLUMN/20051121/224935/
あと、USB 通信の速度が変化しないのは、元々 USB デバイスとの通信速度はUSB Host Controller
|
USB Deviceの間の問題であって、例えば、USB 1.1 と 2.0 で通信速度が異なる。。。と言うことが影響を与える要因で、
PC 側の CPU 速度とはあまり関係無いように思います。
(ファイル保存処理が変化しないのも同様の理由では。。。と勝手に思い込んでいます。)- 回答としてマーク せれ 2011年9月9日 7:55
-
同一PCで 32bit/64bit のデュアルブート環境を構築して、32bit Windows + 32bit App と 64bit Windows + 64bit App と 64bit Windows + 32bit App の組み合わせをそれぞれチェックしないといけないですね。
WOW32 もそうでしたが、WOW も万能ではないので動かない 32bit アプリケーションも時々みかけますね。
# ファイル I/O は、豊富になったメモリ空間を生かしてファイルマップを使うとかすると、ギガ単位クラスの巨大なファイルの処理は 64bit のほうが高速になるかもしれませんね。- 回答としてマーク せれ 2011年9月9日 7:54
すべての返信
-
大前提として、(AWE のような仕組みを使わずに) 32bit にてコンパイル/動作するシステムを 64bit にコンパイルするだけで処理効率が大きくあがることもさがることも、ほぼありません。
.NET のようなオブジェクトを多く扱うシステムでは、ポインタサイズが 32bit から 64bit になることで、処理するデータ量が増加したりメモリ効率が悪くなったり、実行速度面でのパフォーマンスが落ちるという話はあります。(特に、32bit でおさまるメモリ空間でとじたアプリケーションにて)
64bit 演算において無用な変換が削減されることで long の演算速度が向上することはあるのかもしれませんが、32bit アプリケーションであっても long の演算は 64bit 演算が使用されるので、ほとんど差がないと思います。(詳しくないので、全然そんなことはないかもしれません) 64bit 演算ではありませんが、インクリメンタルや 10進数演算は 64bit モードでは使えないものがあるので演算効率が落ちるなどの差異はあるようです。
64bit にすることで、何の効率を上げて表面的なパフォーマンス向上を得るかきちんと計画し、32bit/64bit におけるプロファイルなどをきちんとするべきでしょう。
- 編集済み K. Takaoka 2010年9月10日 3:37
-
すでにご存知かもしれませんが。。。
遅くなった処理部分の理由はわかりませんが、速くなった処理部分に関しては、WOW64 による
Emulation Layer の介在の有無があると思います。
(それが速くなった根本的な要因ではなく、あくまでも速くなった一つの要因。。。と言うことだともいますが。)確か、64 ビット プラットフォーム上で 32 ビット アプリケーションを動作させる場合、必ず WOW64 による
Emulation Layer が介在するはずです。
つまり、32 ビット アプリケーションからのリクエストは全て WOW64 によって 64 ビットのネイティブに変換されて、
最終的に 64 ビットとして処理されるはずで、64 ビット アプリケーションでは WOW64 による Emulation Layer
が介在しないので、そのオーバヘッド部分は確実に速くなるはずだと思います。とりあえず "WOW64" でグーグル先生に聞いてみたら、下記のサイトを教えてくれました。
x64で本格化する64ビットWindowsの時代(3)
http://itpro.nikkeibp.co.jp/article/COLUMN/20051121/224935/
あと、USB 通信の速度が変化しないのは、元々 USB デバイスとの通信速度はUSB Host Controller
|
USB Deviceの間の問題であって、例えば、USB 1.1 と 2.0 で通信速度が異なる。。。と言うことが影響を与える要因で、
PC 側の CPU 速度とはあまり関係無いように思います。
(ファイル保存処理が変化しないのも同様の理由では。。。と勝手に思い込んでいます。)- 回答としてマーク せれ 2011年9月9日 7:55
-
同一PCで 32bit/64bit のデュアルブート環境を構築して、32bit Windows + 32bit App と 64bit Windows + 64bit App と 64bit Windows + 32bit App の組み合わせをそれぞれチェックしないといけないですね。
WOW32 もそうでしたが、WOW も万能ではないので動かない 32bit アプリケーションも時々みかけますね。
# ファイル I/O は、豊富になったメモリ空間を生かしてファイルマップを使うとかすると、ギガ単位クラスの巨大なファイルの処理は 64bit のほうが高速になるかもしれませんね。- 回答としてマーク せれ 2011年9月9日 7:54
-
・longの演算が速くなる
とありますが、これは1クロックで処理できるデータ量が32→64で倍になったという解釈で良いのでしょうか。
違います。32bitモードには64bitの演算命令がありません。そのため32bitモードでの64bit演算は、筆算の要領で32bit演算を組み合わせての演算をします。一方64bitモードには64bitの演算命令があるため、1回の演算で結果が出せます。
というのが基本ですが、32bitモードでもSSE等の64bit演算があったりもして、常に基本通りというわけではありません。それに対してすでに指摘がありますが、メモリアクセスが32bitから64bitと2倍に膨れ上がるので遅くなることがあります。
またこれらはあくまでCPU内部の話であり、USBが64bit化されたり高速化するわけではありません。当然ですが、マウスカーソルが目にもとまらぬ速さで動いたり、ブラインドタッチで超高速なキー入力が実現できるわけでもありません。
それで、せれさんのプログラムは多数の64bit演算を行っていて、その演算時間が支配的なのでしょうか? そうでないのなら速くなるわけがありません。
- 回答としてマーク せれ 2011年9月9日 7:54
-
皆様、たくさんのご返信ありがとうございます。
.NET のようなオブジェクトを多く扱うシステムでは、ポインタサイズが 32bit から 64bit になることで、処理するデータ量が増加したりメモリ効率が悪くなったり、実行速度面でのパフォーマンスが落ちるという話はあります。(特に、32bit でおさまるメモリ空間でとじたアプリケーションにて)
こちらではlogを埋め込んでみた所、InitializeComponentとMainWindow.showで時間がかかっているようでした。
遅くなった処理部分の理由はわかりませんが、速くなった処理部分に関しては、WOW64 による Emulation Layer の介在の有無があると思います。
説明が分かりにくかったようですいません。アプリケーションはx64でコンパイル(64bit化)しているので WOW64の影響は受けていません。
あと、USB 通信の速度が変化しないのは、元々 USB デバイスとの通信速度は USB Host Controller | USB Device の間の問題であって、例えば、USB 1.1 と 2.0 で通信速度が異なる。。。と言うことが影響を与える要因で、 PC 側の CPU 速度とはあまり関係無いように思います。(ファイル保存処理が変化しないのも同様の理由では。。。と勝手に思い込んでいます。)
CPU処理が絡むと処理速度が速くなるということでしょうか。
それで、せれさんのプログラムは多数の64bit演算を行っていて、その演算時間が支配的なのでしょうか? そうでないのなら速くなるわけがありません。
画像に対して2次元フーリエ変換をしています。ここの処理が約2倍速くなっています。ですが64bit演算を意識してコードを記述したわけではないです。 -
画像に対して2次元フーリエ変換をしています。ここの処理が約2倍速くなっています。ですが64bit演算を意識してコードを記述したわけではないです。
そういうことでしたか。32bit CPUは時代とともに命令が順次追加されていて使用するCPUごとに使える命令がバラバラになっています。先のコメントにも「SSE」を挙げましたが、その部分に当たります。で、64bit CPUではその辺りが一旦リセットされ、必ずSSE命令が存在することになっています。(最近はまたバラバラになりつつありますが…w)そのため、たぶん.NET Frameworkも64bitではSSE命令を積極的に使います。
フーリエ変換をされているとのことですが、こういった計算はもともとSSE向きで最適化すれば速くなります。そうでなくてもSSE自身、従来の命令よりも高速ですので、特に意識していなくても2倍速くなったのかもしれません。
- 回答としてマーク せれ 2011年9月9日 7:54
-
そのため、たぶん.NET Frameworkも64bitではSSE命令を積極的に使います。
SSEを積極的に使うということは、32bitでSSEを使ったプログラムを64bit化した速度は同じになるかなと思い、
一般情報を参考に、円周率を求めるプログラムを作って計測しました。C#じゃないですけど。
#include "stdafx.h" #include "SSE.h" #include <math.h> #include <intrin.h> #ifdef _DEBUG #define new DEBUG_NEW #endif // 唯一のアプリケーション オブジェクトです。 CWinApp theApp; using namespace std; int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; // MFC を初期化して、エラーの場合は結果を印刷します。 if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0)) { // TODO: 必要に応じてエラー コードを変更してください。 _tprintf(_T("致命的なエラー: MFC の初期化ができませんでした。\n")); nRetCode = 1; } else { // TODO: アプリケーションの動作を記述するコードをここに挿入してください。 DWORD dwStart = GetTickCount(); int cin = 0; long times=100000000; __m128 rand_max = _mm_set_ss(RAND_MAX); __m128 radius = _mm_set_ss(1); for (int i = 0; i < times; i++){ // x = rand() / RAND_MAX __m128 x = _mm_div_ss(_mm_set_ss(rand()), rand_max); // y = rand() / RAND_MAX __m128 y = _mm_div_ss(_mm_set_ss(rand()), rand_max); // dist = sqrt(x * x + y * y) __m128 dist = _mm_sqrt_ss(_mm_add_ss(_mm_mul_ss(x, x), _mm_mul_ss(y, y))); // result = (dist < 1.0) __m128 result = _mm_cmplt_ss(dist, radius); if (_mm_cvtss_si32(result) != 0) { cin++; } } double pi = 4.0 * (double)cin / (double)times; printf("GetTickCount %d\n", GetTickCount() - dwStart); printf("trial times= %d\n", times); printf("Math.PI = %f\n", 3.14159265358979323846); printf("Calculated = %f\n", pi); } return nRetCode; }
結果は、以下のようになりました。PCはまったく同一スペックです。
32bit Windows + 32bit App : 5.7sec
64bit Windows + 64bit App : 4.2sec
64bit Windows + 32bit App : 5.7sec
32bitの逆アセンブル
for (int i = 0; i < times; i++){ // x = rand() / RAND_MAX __m128 x = _mm_div_ss(_mm_set_ss(rand()), rand_max); 00301080 call dword ptr [__imp__rand (3020A4h)] 00301086 cvtsi2ss xmm0,eax 0030108A xorps xmm1,xmm1 0030108D movss xmm1,xmm0 00301091 movaps xmm0,xmm1 00301094 divss xmm0,dword ptr [esp+20h] 0030109A movaps xmmword ptr [esp+30h],xmm0 // y = rand() / RAND_MAX __m128 y = _mm_div_ss(_mm_set_ss(rand()), rand_max); 0030109F call dword ptr [__imp__rand (3020A4h)] 003010A5 cvtsi2ss xmm0,eax 003010A9 xorps xmm1,xmm1 003010AC movss xmm1,xmm0 003010B0 movaps xmm0,xmm1 003010B3 divss xmm0,dword ptr [esp+20h] // dist = sqrt(x * x + y * y) __m128 dist = _mm_sqrt_ss(_mm_add_ss(_mm_mul_ss(x, x), _mm_mul_ss(y, y))); 003010B9 movaps xmm1,xmmword ptr [esp+30h] 003010BE mulss xmm0,xmm0 003010C2 movaps xmm2,xmm1 003010C5 mulss xmm2,xmm1 003010C9 addss xmm2,xmm0 003010CD sqrtss xmm2,xmm2
64bitの逆アセンブル
for (int i = 0; i < times; i++){ // x = rand() / RAND_MAX __m128 x = _mm_div_ss(_mm_set_ss(rand()), rand_max); 000000013F6C1084 call qword ptr [__imp_rand (13F6C2138h)] 000000013F6C108A xorps xmm6,xmm6 000000013F6C108D movd xmm0,eax 000000013F6C1091 cvtdq2ps xmm0,xmm0 000000013F6C1094 movss xmm6,xmm0 000000013F6C1098 divss xmm6,xmm7 // y = rand() / RAND_MAX __m128 y = _mm_div_ss(_mm_set_ss(rand()), rand_max); 000000013F6C109C call qword ptr [__imp_rand (13F6C2138h)] // dist = sqrt(x * x + y * y) __m128 dist = _mm_sqrt_ss(_mm_add_ss(_mm_mul_ss(x, x), _mm_mul_ss(y, y))); 000000013F6C10A2 mulss xmm6,xmm6 000000013F6C10A6 movd xmm0,eax 000000013F6C10AA cvtdq2ps xmm0,xmm0 000000013F6C10AD xorps xmm1,xmm1 000000013F6C10B0 movss xmm1,xmm0 000000013F6C10B4 divss xmm1,xmm7 000000013F6C10B8 mulss xmm1,xmm1 000000013F6C10BC addss xmm6,xmm1 000000013F6C10C0 sqrtss xmm6,xmm6
000000013F6C109C call qword ptr [__imp_rand (13F6C2138h)]
ここが早くなっている肝なのでしょうか。
-
ぱっとみ、レジスタ数の差異が顕著に表れてそうですね。32bit 版は使えるレジスタが少ないのでメモリ I/O が多量に発生してしまっているようにみえます。
xmm6,xmm7は32bitでも扱えますので、私はレジスタ使用の最適化かな、と思っています。また、フーリエ変換しているところを逆アセンブルすると佐祐理さんのおっしゃる通り、SSE命令が使われていました。画像処理が早くなった要因はSSE命令が積極的に使われているからみたいですね。
また、K.Takaokaさんの指摘通り、同一PCで 32bit/64bit のデュアルブート環境を構築して、32bit Windows + 32bit App と 64bit Windows + 64bit App と 64bit Windows + 32bit App の組み合わせをチェックした所、なんとアプリの起動速度はエミュレーションが一番速い結果になりました。(下記参照)。その他、ファイル保存やUSB通信はエミュレーションでも速度は変わらずでした。
32bit Windows + 32bit:4.5秒
64bit Windows + 32bit App:4秒
64bit Windows + 64bit App:5.5秒
さて、この起動速度、32bitより遅いというならWOW64 によるEmulation Layer が介在する為かなと考察できますが、速くなるのはどうしてでしょうか。
ちなみに他に作成したC#アプリも動かしてみましたが程度の差こそあれ、64bit Windows + 32bit Appの組み合わせが一番速かったです。
-
どのような測定方法をされたのかにもよります。Windowsにはプリフェッチ機能があり何度か実行するとこなれてきたり、64bitのあとにWOW64で試すと64bitの処理中に読み込んだファイルがキャッシュに残っていたり。
またスピードの出方にも違いはあると思います。演算は速くなるけどUIが遅くなったとか。
はたまた測定中にバックグラウンドで重い別プロセスが動いていてそれに引きずられたり.NETアプリは初回は起動が遅いので、2回目以降のキャッシュが効いた状態での起動時間を測定しています
また、他の重い処理に引きずられないよう注意もしております
測定間隔はApplication_StartupからMainWindowの WindowLoadedまでです。
上記の中でも細かくログを埋め込んでみましたが、特に差が顕著なのが以下の2つです。
1. InitializeComponentの前後 (WOW64 > 32bit > 64bit)
2. MainWindow.ShowからWindowsLoadedまで(WOW64 = 32bit > 64bit)