none
64bit化による処理速度の影響 RRS feed

  • 質問

  • お世話になります。

    Windows 7 Pro Visual Studio2008でC++、C#のアプリケーションを開発しています。

    処理速度の向上を期待してアプリケーションの64bit化を行っていたのですが、私のアプリでは、64bit化によって起動速度は1秒ほど遅くなり、画像処理速度は約2倍に向上し、USB通信によるデータの転送やファイル保存処理は速度が変わりませんでした。

    いろいろ検索してみたのですが、処理速度が向上あるいは劣化した報告は多数あるのですが、どういう理由で速度に影響を与えているのかを記述している記事になかなか巡りあえずここで質問させてもらいました。

    例えば以下の記事で

    http://social.msdn.microsoft.com/Forums/ja-JP/csharpgeneralja/thread/fcb327d4-a9f7-43ad-ab00-ce91c080b931

     ・longの演算が速くなる

    とありますが、これは1クロックで処理できるデータ量が32→64で倍になったという解釈で良いのでしょうか。

    その他にも速度が速くなるなら速くなる理由、遅くなるなら遅くなる理由が知りたいと思っています。

     

    どうかご教授の程よろしくお願いします。

     

     

    2010年9月9日 8:55

回答

  •  ・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
    2010年9月10日 0:00
  • 画像に対して2次元フーリエ変換をしています。ここの処理が約2倍速くなっています。ですが64bit演算を意識してコードを記述したわけではないです。

    そういうことでしたか。32bit CPUは時代とともに命令が順次追加されていて使用するCPUごとに使える命令がバラバラになっています。先のコメントにも「SSE」を挙げましたが、その部分に当たります。で、64bit CPUではその辺りが一旦リセットされ、必ずSSE命令が存在することになっています。(最近はまたバラバラになりつつありますが…w)そのため、たぶん.NET Frameworkも64bitではSSE命令を積極的に使います。

    フーリエ変換をされているとのことですが、こういった計算はもともとSSE向きで最適化すれば速くなります。そうでなくてもSSE自身、従来の命令よりも高速ですので、特に意識していなくても2倍速くなったのかもしれません。

    • 回答としてマーク せれ 2011年9月9日 7:54
    2010年9月13日 1:39
  • すでにご存知かもしれませんが。。。

    遅くなった処理部分の理由はわかりませんが、速くなった処理部分に関しては、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
    2010年9月9日 11:31
  • 同一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
    2010年9月9日 23:46

すべての返信

  • 大前提として、(AWE のような仕組みを使わずに) 32bit にてコンパイル/動作するシステムを 64bit にコンパイルするだけで処理効率が大きくあがることもさがることも、ほぼありません。

    .NET のようなオブジェクトを多く扱うシステムでは、ポインタサイズが 32bit から 64bit になることで、処理するデータ量が増加したりメモリ効率が悪くなったり、実行速度面でのパフォーマンスが落ちるという話はあります。(特に、32bit でおさまるメモリ空間でとじたアプリケーションにて)

    64bit 演算において無用な変換が削減されることで long の演算速度が向上することはあるのかもしれませんが、32bit アプリケーションであっても long の演算は 64bit 演算が使用されるので、ほとんど差がないと思います。(詳しくないので、全然そんなことはないかもしれません) 64bit 演算ではありませんが、インクリメンタルや 10進数演算は 64bit モードでは使えないものがあるので演算効率が落ちるなどの差異はあるようです。

    64bit にすることで、何の効率を上げて表面的なパフォーマンス向上を得るかきちんと計画し、32bit/64bit におけるプロファイルなどをきちんとするべきでしょう。

    2010年9月9日 9:56
  • すでにご存知かもしれませんが。。。

    遅くなった処理部分の理由はわかりませんが、速くなった処理部分に関しては、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
    2010年9月9日 11:31
  • 同一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
    2010年9月9日 23:46
  •  ・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
    2010年9月10日 0:00
  • 皆様、たくさんのご返信ありがとうございます。
    .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演算を意識してコードを記述したわけではないです。
    2010年9月13日 1:12
  • 画像に対して2次元フーリエ変換をしています。ここの処理が約2倍速くなっています。ですが64bit演算を意識してコードを記述したわけではないです。

    そういうことでしたか。32bit CPUは時代とともに命令が順次追加されていて使用するCPUごとに使える命令がバラバラになっています。先のコメントにも「SSE」を挙げましたが、その部分に当たります。で、64bit CPUではその辺りが一旦リセットされ、必ずSSE命令が存在することになっています。(最近はまたバラバラになりつつありますが…w)そのため、たぶん.NET Frameworkも64bitではSSE命令を積極的に使います。

    フーリエ変換をされているとのことですが、こういった計算はもともとSSE向きで最適化すれば速くなります。そうでなくてもSSE自身、従来の命令よりも高速ですので、特に意識していなくても2倍速くなったのかもしれません。

    • 回答としてマーク せれ 2011年9月9日 7:54
    2010年9月13日 1:39
  • そのため、たぶん.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)]

    ここが早くなっている肝なのでしょうか。

     

    2010年9月14日 7:39
  • ぱっとみ、レジスタ数の差異が顕著に表れてそうですね。32bit 版は使えるレジスタが少ないのでメモリ I/O が多量に発生してしまっているようにみえます。

    もちろん、これも 64bit 化による恩恵だと思います。SSE を利用した演算処理の速度差を求めたい場合、もうすこし使用する変数を減らして32bit 版のコンパイラがレジスタだけに最適化できるようにしてあげるとよいかもしれませんね。

    2010年9月14日 9:01
  • ぱっとみ、レジスタ数の差異が顕著に表れてそうですね。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の組み合わせが一番速かったです。

     

    2010年9月16日 7:59
  • どのような測定方法をされたのかにもよります。Windowsにはプリフェッチ機能があり何度か実行するとこなれてきたり、64bitのあとにWOW64で試すと64bitの処理中に読み込んだファイルがキャッシュに残っていたり。
    またスピードの出方にも違いはあると思います。演算は速くなるけどUIが遅くなったとか。
    はたまた測定中にバックグラウンドで重い別プロセスが動いていてそれに引きずられたり。

    というわけでこの4~5.5秒の内訳が重要に思います。

    # 明確な答えを持っていなくてすみません。

    2010年9月18日 7:25
  • どのような測定方法をされたのかにもよります。Windowsにはプリフェッチ機能があり何度か実行するとこなれてきたり、64bitのあとにWOW64で試すと64bitの処理中に読み込んだファイルがキャッシュに残っていたり。
    またスピードの出方にも違いはあると思います。演算は速くなるけどUIが遅くなったとか。
    はたまた測定中にバックグラウンドで重い別プロセスが動いていてそれに引きずられたり

    .NETアプリは初回は起動が遅いので、2回目以降のキャッシュが効いた状態での起動時間を測定しています

    また、他の重い処理に引きずられないよう注意もしております

    測定間隔はApplication_StartupからMainWindowの WindowLoadedまでです。

    上記の中でも細かくログを埋め込んでみましたが、特に差が顕著なのが以下の2つです。

    1. InitializeComponentの前後 (WOW64 > 32bit > 64bit)

    2. MainWindow.ShowからWindowsLoadedまで(WOW64 = 32bit > 64bit)

     

     

    2010年9月20日 23:56