質問

XP64を搭載したCore2マシン(Dell T5400)があります。

32ビットプロセスを実行すると、 memcpyのパフォーマンスは 1.2GByte / s;ただし、64ビットプロセスのmemcpy 約2.2GByte / s(または2.4GByte / s IntelコンパイラCRTのmemcpyを使用)。ながら 最初の反応はこれを説明するだけかもしれません より広いレジスタが利用できるため 64ビットコードでは、独自のmemcpyのような SSEアセンブリコード(128ビットを使用する必要があります) 32/64ビットの大きさに関係なく、幅広いロードストア プロセス)で同様の上限を示します 達成するコピー帯域幅。

私の質問は、実際にこの違いは何ですか のため ? 32ビットプロセスをジャンプする必要があるか RAMを取得するためのいくつかの追加のWOW64フープ?何かありますか TLBまたはプリフェッチャーを使用するか、または...何ですか?

ご意見ありがとうございます。

Intelフォーラムでも発生

役に立ちましたか?

解決

もちろん、デバッガーでマシンコードにステップインすることにより、memcpyの最も内側のループ内で実行されている実際のマシン命令を確認する必要があります。それ以外は単なる憶測です。

私の質問は、32ビット自体と64ビットそれ自体にはおそらく何の関係もないということです。私の推測では、より高速なライブラリルーチンは、SSEの非テンポラルストアを使用して記述されています。

内部ループに従来のロード/ストア命令のバリエーションが含まれている場合、 次に、宛先メモリをマシンのキャッシュに読み込み、変更し、書き戻す必要があります。その読み取りはまったく不要であるため(読み取り中のビットはすぐに上書きされます)、<!> quot; non-temporal <!> quot;を使用して、メモリ帯域幅の半分を節約できます。キャッシュをバイパスする命令を記述します。このように、宛先メモリは、往復ではなく片方向でメモリに書き込まれます。

IntelコンパイラのCRTライブラリを知らないので、これは単なる推測です。 32ビットのlibCRTで同じことができない理由は特にありませんが、引用したスピードアップは、movdqa命令をmovntに変換するだけで予想されるものです...

memcpyは計算を行わないため、メモリの読み取りと書き込みの速度に常に制約されます。

他のヒント

次のように説明できると思います:

メモリからレジスタにデータをコピーしてメモリに戻すには、

mov eax, [address]
mov [address2], eax

これにより、32ビット(4バイト)がアドレスからアドレス2に移動します。 64ビットモードの64ビットでも同じことが言えます

mov rax, [address]
mov [address2], rax

これにより、64ビット、2バイトがアドレスからアドレス2に移動します。 <!> quot; mov <!> quot; Intelの仕様によれば、64ビットか32ビットかに関係なく、0.5のレイテンシと0.5のスループットがあります。レイテンシとは、命令がパイプラインを移動するのに必要なクロックサイクルの数であり、スループットとは、同じ命令を再度受け入れる前にCPUが待機しなければならない時間のことです。ご覧のとおり、クロックサイクルごとに2つのmovを実行できますが、2つのmovの間に半クロックサイクル待機する必要があるため、事実上、クロックサイクルごとに1つのmovしか実行できません(または、ここで間違って用語を誤解していますか?詳細については、こちらのPDF を参照してください。

もちろん、mov reg, memは、データが1次キャッシュまたは2次キャッシュにあるか、キャッシュにないか、およびメモリから取得する必要があるかによって、0.5サイクルより長くなる可能性があります。ただし、上記のレイテンシ時間はこの事実を無視します(上記でリンクしたPDFの状態)ので、movに必要なすべてのデータが既に存在すると仮定します(そうでない場合、どこからでもデータをフェッチするのにかかる時間によってレイテンシが増加します)現時点では、これは数クロックサイクルである可能性があり、実行されるコマンドとは完全に独立しており、PDFの482 / C-30ページに記載されています。

興味深いのは、movが32ビットか64ビットかは関係ありません。つまり、メモリ帯域幅が制限要因にならない限り、64ビットmovは32ビットmovと同等に高速であり、64ビットを使用する場合に同じ量のデータをAからBに移動するのに必要なmovの数は半分であるため、スループットは(理論上)2倍になります(そうでないという事実は、おそらくメモリが無制限に高速ではないためです)。

さて、今、あなたはより大きなSSEレジスタを使用するとき、より速いスループットを得るべきだと思いますか?私の知る限り、xmmレジスタは256ではなく、128ビット幅です。BTW( Wikipediaのリファレンス)。ただし、遅延とスループットを考慮しましたか?移動するデータが128ビットにアラインされているかどうか。それに応じて、次のいずれかを使用して移動します

movdqa xmm1, [address]
movdqa [address2], xmm1

または整列されていない場合

movdqu xmm1, [address]
movdqu [address2], xmm1

まあ、movdqa / movdquのレイテンシは1、スループットは1です。したがって、命令の実行には2倍の時間がかかり、命令後の待機時間は通常のmovの2倍になります。

そして、CPUが実際に命令をマイクロオペレーションに分割し、それらを並列に実行できるという事実も考慮していません。今では本当に複雑になり始めています...私にとっても複雑すぎます。

とにかく、xmmレジスタへ/からデータをロードする経験は、通常のレジスタへ/からデータをロードするよりもはるかに遅いことを知っているので、xmmレジスタを使用して転送を高速化するというアイデアは最初の1秒から運命づけられました。実際、SSEのmemmoveが通常のmemmoveよりも遅くないことに驚いています。

私はついにこれの一番下に到達しました(そして、Senteの答えのDieは正しい行にありました、ありがとう)

以下では、dstとsrcは512 MByte std :: vectorです。 Intel 10.1.029コンパイラとCRTを使用しています。

64ビット両方

  

memcpy(&dst[0],&src[0],dst.size())

and

  

memcpy(&dst[0],&src[0],N)

Nは以前に宣言されたconst size_t N=512*(1<<20); 電話する

  

__intel_fast_memcpy

その大部分は次のもので構成されています:

  000000014004ED80  lea         rcx,[rcx+40h] 
  000000014004ED84  lea         rdx,[rdx+40h] 
  000000014004ED88  lea         r8,[r8-40h] 
  000000014004ED8C  prefetchnta [rdx+180h] 
  000000014004ED93  movdqu      xmm0,xmmword ptr [rdx-40h] 
  000000014004ED98  movdqu      xmm1,xmmword ptr [rdx-30h] 
  000000014004ED9D  cmp         r8,40h 
  000000014004EDA1  movntdq     xmmword ptr [rcx-40h],xmm0 
  000000014004EDA6  movntdq     xmmword ptr [rcx-30h],xmm1 
  000000014004EDAB  movdqu      xmm2,xmmword ptr [rdx-20h] 
  000000014004EDB0  movdqu      xmm3,xmmword ptr [rdx-10h] 
  000000014004EDB5  movntdq     xmmword ptr [rcx-20h],xmm2 
  000000014004EDBA  movntdq     xmmword ptr [rcx-10h],xmm3 
  000000014004EDBF  jge         000000014004ED80 

〜2200 MByte / sで実行されます。

ただし、32ビットでは

  

_mm_stream_ps

通話

  

dst.size()

の大部分は

  004447A0  sub         ecx,80h 
  004447A6  movdqa      xmm0,xmmword ptr [esi] 
  004447AA  movdqa      xmm1,xmmword ptr [esi+10h] 
  004447AF  movdqa      xmmword ptr [edx],xmm0 
  004447B3  movdqa      xmmword ptr [edx+10h],xmm1 
  004447B8  movdqa      xmm2,xmmword ptr [esi+20h] 
  004447BD  movdqa      xmm3,xmmword ptr [esi+30h] 
  004447C2  movdqa      xmmword ptr [edx+20h],xmm2 
  004447C7  movdqa      xmmword ptr [edx+30h],xmm3 
  004447CC  movdqa      xmm4,xmmword ptr [esi+40h] 
  004447D1  movdqa      xmm5,xmmword ptr [esi+50h] 
  004447D6  movdqa      xmmword ptr [edx+40h],xmm4 
  004447DB  movdqa      xmmword ptr [edx+50h],xmm5 
  004447E0  movdqa      xmm6,xmmword ptr [esi+60h] 
  004447E5  movdqa      xmm7,xmmword ptr [esi+70h] 
  004447EA  add         esi,80h 
  004447F0  movdqa      xmmword ptr [edx+60h],xmm6 
  004447F5  movdqa      xmmword ptr [edx+70h],xmm7 
  004447FA  add         edx,80h 
  00444800  cmp         ecx,80h 
  00444806  jge         004447A0

〜1350 MByte / sのみで実行されます。

HOWEVER

memcpy(&dst[0],&src[0],N)

Nは以前に宣言されているmovntは、32ビットで)を直接呼び出してコンパイルします

__intel_VEC_memcpy

の大部分は

  0043FF40  movdqa      xmm0,xmmword ptr [esi] 
  0043FF44  movdqa      xmm1,xmmword ptr [esi+10h] 
  0043FF49  movdqa      xmm2,xmmword ptr [esi+20h] 
  0043FF4E  movdqa      xmm3,xmmword ptr [esi+30h] 
  0043FF53  movntdq     xmmword ptr [edi],xmm0 
  0043FF57  movntdq     xmmword ptr [edi+10h],xmm1 
  0043FF5C  movntdq     xmmword ptr [edi+20h],xmm2 
  0043FF61  movntdq     xmmword ptr [edi+30h],xmm3 
  0043FF66  movdqa      xmm4,xmmword ptr [esi+40h] 
  0043FF6B  movdqa      xmm5,xmmword ptr [esi+50h] 
  0043FF70  movdqa      xmm6,xmmword ptr [esi+60h] 
  0043FF75  movdqa      xmm7,xmmword ptr [esi+70h] 
  0043FF7A  movntdq     xmmword ptr [edi+40h],xmm4 
  0043FF7F  movntdq     xmmword ptr [edi+50h],xmm5 
  0043FF84  movntdq     xmmword ptr [edi+60h],xmm6 
  0043FF89  movntdq     xmmword ptr [edi+70h],xmm7 
  0043FF8E  lea         esi,[esi+80h] 
  0043FF94  lea         edi,[edi+80h] 
  0043FF9A  dec         ecx  
  0043FF9B  jne         ___intel_VEC_memcpy+244h (43FF40h) 

〜2100MByte / sで実行します(32ビットを証明するのに帯域幅が制限されているわけではありません)。

私は自分のmemcpyのようなSSEコードが 32ビットビルドで同様の〜1300 MByte / limit;今は問題ありません 32ビットまたは64ビットで<!> gt; 2GByte / sを取得。トリック(上記の結果のヒントとして) 一時的でない(<!> quot; streaming <!> quot;)ストア(たとえばCPUID組み込み)を使用することです。

32ビットの<!> quot; <=> <!> quot; memcpyは最終的にはしません より高速な<!> quot; <=> <!> quotを呼び出します。バージョン(memcpyに足を踏み入れると、 驚異的な量の<=>チェックとヒューリスティックロジック(例:数値の比較) あなたの近くのどこかに行く前にキャッシュサイズなどでコピーされるバイト数 実際のデータ)しかし、少なくとも私は現在、観察された動作を理解しています(そしてそれは SysWow64またはハードウェア関連ではありません)。

オフカフの推測では、64ビットプロセスはプロセッサのネイティブ64ビットメモリサイズを使用しているため、メモリバスの使用が最適化されます。

正のフィードバックをありがとう!ここで何が起こっているかを部分的に説明できると思います。

memcpyに一時的でないストアを使用することは、memcpy呼び出しのタイミングを計っている場合のみ 断食です。

一方、アプリケーションのベンチマークを行っている場合、movdqaストアには、宛先メモリをキャッシュに残しておくという利点があります。または、少なくともキャッシュに収まる部分。

したがって、ランタイムライブラリを設計していて、memcpyを呼び出したアプリケーションがmemcpy呼び出しの直後に宛先バッファーを使用すると想定できる場合は、movdqaバージョンを提供する必要があります。これにより、メモリからmovntdqバージョンに続くCPUへの戻りが効果的に最適化され、呼び出しに続くすべての命令が高速に実行されます。

しかし、一方で、宛先バッファーがプロセッサーのキャッシュと比較して大きい場合、その最適化は機能せず、movntdqバージョンはより速いアプリケーションベンチマークを提供します。

したがって、memcpyのアイデアには、複数のバージョンがあります。宛先バッファーがプロセッサーのキャッシュと比較して小さい場合はmovdqaを使用し、そうでない場合は宛先バッファーがプロセッサーのキャッシュと比較して大きい場合はmovntdqを使用します。これが32ビットライブラリで起こっていることのように思えます。

もちろん、これは32ビットと64ビットの違いとは関係ありません。

私の推測では、64ビットライブラリはそれほど成熟していないということです。開発者は、そのバージョンのライブラリで両方のルーチンをまだ提供していません。

目の前に参考文献がないので、タイミング/指示については絶対に肯定的ではありませんが、理論を与えることはできます。 32ビットモードでメモリ移動を実行している場合、<!> quot; rep movsd <!> quot;クロックサイクルごとに単一の32ビット値を移動します。 64ビットモードでは、<!> quot; rep movsq <!> quot;クロックサイクルごとに単一の64ビット移動を行います。この命令は32ビットコードでは使用できないため、実行速度の半分で2 x rep movsd(1サイクルで1サイクル)を実行することになります。

非常に単純化され、すべてのメモリ帯域幅/アライメントの問題などを無視しますが、これがすべての始まりです...

64ビットアーキテクチャ専用のmemcpyルーチンの例を次に示します。

void uint8copy(void *dest, void *src, size_t n){
    uint64_t * ss = (uint64_t)src;
    uint64_t * dd = (uint64_t)dest;
    n = n * sizeof(uint8_t)/sizeof(uint64_t); 

    while(n--)
        *dd++ = *ss++;
}//end uint8copy()

記事全文はこちら: http://www.godlikemouse.com/2008/03/04/ optimize-memcpy-routines /

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top