web-dev-qa-db-ja.com

画像処理のための非常に速いmemcpy?

私は、メモリ内で大量のデータをコピーする必要があるCで画像処理を行っています。ソースと宛先が重複することはありません。

[〜#〜] gcc [〜#〜] を使用してx86プラットフォームでこれを行うための絶対最速の方法は何ですか(ここで [〜#〜] sse [〜#〜] 、SSE2は使用できますが、SSE3は使用できません)?

ソリューションはアセンブリにあるか、GCC組み込み関数を使用することになると思いますか?

私は次のリンクを見つけましたが、それが最善の方法であるかどうかわかりません(著者にもいくつかのバグがあると言われています): http://coding.derkeiler.com/Archive/Assembler/comp。 lang.asm.x86/2006-02/msg00123.html

編集:コピーが必要であることに注意してください。データをコピーする必要はありません(理由は説明できますが、説明は省略します:))

31
horseyguy

William Chan およびGoogleの厚意により掲載。 Microsoft Visual Studio 2005のmemcpyより30〜70%高速。

void X_aligned_memcpy_sse2(void* dest, const void* src, const unsigned long size)
{

  __asm
  {
    mov esi, src;    //src pointer
    mov edi, dest;   //dest pointer

    mov ebx, size;   //ebx is our counter 
    shr ebx, 7;      //divide by 128 (8 * 128bit registers)


    loop_copy:
      prefetchnta 128[ESI]; //SSE2 prefetch
      prefetchnta 160[ESI];
      prefetchnta 192[ESI];
      prefetchnta 224[ESI];

      movdqa xmm0, 0[ESI]; //move data from src to registers
      movdqa xmm1, 16[ESI];
      movdqa xmm2, 32[ESI];
      movdqa xmm3, 48[ESI];
      movdqa xmm4, 64[ESI];
      movdqa xmm5, 80[ESI];
      movdqa xmm6, 96[ESI];
      movdqa xmm7, 112[ESI];

      movntdq 0[EDI], xmm0; //move data from registers to dest
      movntdq 16[EDI], xmm1;
      movntdq 32[EDI], xmm2;
      movntdq 48[EDI], xmm3;
      movntdq 64[EDI], xmm4;
      movntdq 80[EDI], xmm5;
      movntdq 96[EDI], xmm6;
      movntdq 112[EDI], xmm7;

      add esi, 128;
      add edi, 128;
      dec ebx;

      jnz loop_copy; //loop please
    loop_copy_end:
  }
}

正確な状況や想定できる仮定によっては、さらに最適化できる場合があります。

Memcpyソース(memcpy.asm)をチェックアウトして、その特別なケースの処理を取り除くこともできます。さらに最適化できる可能性があります!

40
hplbsh

-O1以上の最適化レベルでは、GCCはmemcpyのような関数に組み込みの定義を使用します-正しい-marchパラメータ(-march=pentium4は、あなたが言及する機能のセットに対して) )それはかなり最適なアーキテクチャ固有のインラインコードを生成するはずです。

私はそれをベンチマークし、何が出てくるか見てみます。

6
caf

この質問は4歳になりましたが、メモリ帯域幅についてまだ誰も言及していません。 CPU-Zは、私のマシンにPC3-10700 RAMがあると報告しています。 RAMのピーク帯域幅(転送速度、スループットなど)は10700 MBytes /秒)です。私のマシンのCPUはi5-2430M CPUで、ピークターボ周波数は3 GHzです。

理論的には、CPUとRAMが無限に速い場合、memcpyは5300 MBytes/sec、つまり10700の半分になる可能性があります。これは、memcpyがRAMから読み取り、RAMに書き込む必要があるためです。 (編集:v.oddouが指摘したように、これは単純化した近似です)。

一方、無限に高速なRAMと現実的なCPUがあるとしたら、何ができるでしょうか?例として3 GHzのCPUを使用してみましょう。32ビットの読み取りが可能で、サイクルごとに32ビットの書き込みを行うと、3e9 * 4 = 12000 MBytes/secが転送される可能性があります。これは、最近のCPUでは簡単に到達できるようです。すでに、CPUで実行されているコードを確認できますこれは実際のボトルネックではありません。これが、最新のマシンにデータキャッシュがある理由の1つです。

データがキャッシュされていることがわかっているときにmemcpyをベンチマークすることで、CPUが実際に何ができるかを測定できます。これを正確に行うのは面倒です。乱数を配列に書き込む単純なアプリを作成し、それらを別の配列にmemcpyした後、コピーしたデータをチェックサムしました。デバッガーのコードをステップ実行して、賢いコンパイラーがコピーを削除していないことを確認しました。配列のサイズを変更すると、キャッシュのパフォーマンスが変更されます-小さい配列はキャッシュに収まり、大きい配列はキャッシュに収まります。次の結果が得られました。

  • 40 Kバイトアレイ:16000 Mバイト/秒
  • 400 KByteアレイ:11000 MBytes /秒
  • 4000 KByteアレイ:3100 MBytes /秒

16000は上記で理論的に計算した12000よりも大きいので、CPUは1サイクルあたり32ビットを超えるデータを読み書きできることは明らかです。これは、CPUがすでに思っていたよりもボトルネックが少ないことを意味します。 Visual Studio 2005を使用して、標準のmemcpy実装にステップインすると、マシンでmovqda命令を使用していることがわかります。これはサイクルごとに64ビットを読み書きできると思います。

投稿されたniceコードhapalibashiは、私のマシンで4200 MBytes/secを達成しています。これは、VS 2005の実装よりも約40%高速です。キャッシュのパフォーマンスを向上させるためにプリフェッチ命令を使用するので、それはより速いと思います。

要約すると、CPUで実行されるコードはボトルネックではなく、そのコードを調整しても小さな改善しかありません。

6

Hapalibashiによって投稿されたSSEコードは、進むべき道です。

さらに高いパフォーマンスが必要で、デバイスドライバーを作成する長く曲がりくねった道を避けない場合:すべての重要なプラットフォームには、最近、コピージョブをより速く、CPUコードと並行して実行できるDMAコントローラーがあります出来ました。

ただし、これにはドライバーの作成が含まれます。私が認識している大きなOSは、セキュリティリスクのためにこの機能をユーザー側に公開していません。

ただし、そのような作業を行うように設計されたハードウェアの一部を地上で実行できるコードがないため、(パフォーマンスが必要な場合は)価値があるかもしれません。

6

Intelプロセッサに固有の場合は、 [〜#〜] ipp [〜#〜] の恩恵を受ける可能性があります。 Nvidia GPUで実行されることがわかっている場合は、おそらく [〜#〜] cuda [〜#〜] を使用できます-どちらの場合も、memcpy()を最適化するよりも幅を広くする方が良いでしょう-それらより高いレベルでアルゴリズムを改善する機会を提供します。ただし、どちらも特定のハードウェアに依存しています。

3
Clifford

Windowsを使用している場合は、特定の [〜#〜] gpu [〜#〜] -グラフィックス処理に最適化されたルーチンがある DirectX APIを使用します(速度あなたのCPUはロードされていません。GPUがそれを実行している間に他のことをしてください)。

OSにとらわれないようにするには、 OpenGL を試してください。

アセンブラをいじらないでください。10年以上の熟練したライブラリ作成ソフトウェアエンジニアのパフォーマンスを無残に失敗する可能性が非常に高いためです。

2
jpinto3912