2013-06-13 2 views
8

私は2つの配列を持っています。例えば、私はストライドデータをC++でコピーする

A A A A A A A A ... 

B B B B B B B B ... 

を持っていると私はポスト「Is there a standard, strided version of memcpy?」から

B A A B A A B A ... 

を得ることがABのすべての三つの要素をコピーしたい、可能性には、そのような存在しないようですしかし、いくつかのケースでは、memcpyがループベースのコピーforより高速であることを経験しました。

私の質問は、 C言語でストライドメモリコピーを効率的に実行する方法はありますか?forループのように少なくとも実行していますか?

ありがとうございました。

EDIT - 問題をより明確にすることが課題

の明確化、私たちはabによって手で二つの配列を示すものとします。私はユニーク以下を実行する関数forループ

、彼らが実際に意味することができるように、両方の []年代は、例えば

、(私は式テンプレート技術を使用しています)の演算子をオーバーロードされ
for (int i=0; i<NumElements, i++) 
    a_[i] = b_[i]; 

を持っています

a[3*i]=b[i]; 
+0

ループの標準は、少なくともループの標準と同じ速さで実行されます。皮肉なことに、それはあなたが使用しているデータ記憶構造に依存します。配列の場合、私はあなたのモジュラスで増分されたforループよりも優れているとは思いません。 – ChrisCM

+0

'memcpy'は、それが動作しているメモリが連続しているために実行できる最適化のために、' for'ループより速いことがあります。これらの最適化はここではできません。 –

+0

@dauphicしかし、なぜCUDAにピッチをコピーする 'cudaMemcpy2D'があるのですか? – JackOLantern

答えて

6
Is there any way to efficiently perform strided memory copy in C++ performing at least as a standard for loop? 

編集2:ストライドC++ライブラリにコピーする機能はありません。

ストライドコピーは一般的にメモリコピーではないため、チップメーカーも言語設計もストライドコピーを特化してサポートしています。

標準のforループを想定すると、ループアンロールを使用すると、パフォーマンスが向上する場合があります。いくつかのコンパイラには、ループをアンロールするオプションがあります。それは「標準的な」オプションではない。 繰り出さバージョンで

for (index_result = 0; index_result < RESULT_SIZE;) 
{ 
    result[index_result++] = B[index_b++]; 
    result[index_result++] = A[index_a++]; 
    result[index_result++] = A[index_a++]; 

    result[index_result++] = B[index_b++]; 
    result[index_result++] = A[index_a++]; 
    result[index_result++] = A[index_a++]; 
} 

、数:

#define RESULT_SIZE 72 
#define SIZE_A 48 
#define SIZE_B 24 

unsigned int A[SIZE_A]; 
unsigned int B[SIZE_B]; 
unsigned int result[RESULT_SIZE]; 

unsigned int index_a = 0; 
unsigned int index_b = 0; 
unsigned int index_result = 0; 
for (index_result = 0; index_result < RESULT_SIZE;) 
{ 
    result[index_result++] = B[index_b++]; 
    result[index_result++] = A[index_a++]; 
    result[index_result++] = A[index_a++]; 
} 

ループのアンロール "標準" forループの内容を繰り返します:標準forループを考えると

ループは半分にカットされています。

パフォーマンスの向上は、他のオプションと比較してごくわずかです。 次の問題は、パフォーマンスに影響を与え、それぞれが異なる速度の向上を有していてもよい:

  • 処理データキャッシュは
  • 命令パイプラインのリロード(プロセッサに依存します)
  • オペレーティングシステムディスクとメモリをスワップ
  • 他のタスクをミス同時に実行する
  • 並列処理(プロセッサ/プラットフォームによって異なる)

並列処理の1つの例は、1つのプロセッサにBアイテムを新しいアレイにコピーさせ、別のプロセッサがAアイテムを新しいアレイにコピーさせることです。

+0

あなたの優しい答えをありがとう。問題をより詳しく説明するために投稿を編集しました。あなたは '#pragma unroll'によって状況を改善するチャンスがあると思いますか?私はコピーについてのすべてが実行時に分かっているので、分かりません。 – JackOLantern

+0

私が言ったように、プロセッサに依存します。いくつかのプロセッサでは、分岐が命令パイプラインをフラッシュし、プロセッサはそれをリロードする必要があります。現代のプロセッサには、命令キャッシュ内に単純なforループを保持し、リロードする必要のない十分な命令パイプラインキャッシュがあります。 –

+0

ほとんどのプロセッサは、順次命令の実行を優先し、分岐命令に遭遇したくないと考えています。 –

6

あまりにも具体的な答えかもしれませんが、NEONをサポートするARMプラットフォームでは、ストライドコピーをさらに高速化するためにNEONベクトル化を使用できます。これは、リソースが比較的限られている環境では人生を節約することができます。これはおそらく、最初にARMをその環境で使用する理由です。目立った例は、ほとんどのデバイスがNEONをサポートするARM v7aアーキテクチャを引き続き使用するAndroidです。

次の例では、YUV420spイメージの半平面UV平面をYUV420pイメージの平面UV平面にコピーするループを示しています。送信元バッファと宛先バッファのサイズは、両方とも640*480/2バイトです。すべての例は、Android NDK r9dのg ++​​ 4.8でコンパイルされています。

レベル1:彼らはサムスンExynosオクタ5420プロセッサ上で実行されているだけ-O3でコンパイル正規

void convertUVsp2UVp(
    unsigned char* __restrict srcptr, 
    unsigned char* __restrict dstptr, 
    int stride) 
{ 
    for(int i=0;i<stride;i++){ 
     dstptr[i]   = srcptr[i*2]; 
     dstptr[i + stride] = srcptr[i*2 + 1]; 
    } 
} 

、平均で約1.5ミリ秒かかります。

レベル2:広げとは-O3のみ、平均で約1.15ミリ秒かかるとコンパイル移動ポインタ

void convertUVsp2UVp(
    unsigned char* __restrict srcptr, 
    unsigned char* __restrict dstptr, 
    int stride) 
{ 
    unsigned char* endptr = dstptr + stride; 
    while(dstptr<endptr){ 
     *(dstptr + 0)    = *(srcptr + 0); 
     *(dstptr + stride + 0) = *(srcptr + 1); 
     *(dstptr + 1)    = *(srcptr + 2); 
     *(dstptr + stride + 1) = *(srcptr + 3); 
     *(dstptr + 2)    = *(srcptr + 4); 
     *(dstptr + stride + 2) = *(srcptr + 5); 
     *(dstptr + 3)    = *(srcptr + 6); 
     *(dstptr + stride + 3) = *(srcptr + 7); 
     *(dstptr + 4)    = *(srcptr + 8); 
     *(dstptr + stride + 4) = *(srcptr + 9); 
     *(dstptr + 5)    = *(srcptr + 10); 
     *(dstptr + stride + 5) = *(srcptr + 11); 
     *(dstptr + 6)    = *(srcptr + 12); 
     *(dstptr + stride + 6) = *(srcptr + 13); 
     *(dstptr + 7)    = *(srcptr + 14); 
     *(dstptr + stride + 7) = *(srcptr + 15); 
     srcptr+=16; 
     dstptr+=8; 
    } 
} 

で少し絞っ。これはおそらく、他の答えに従って、通常のアーキテクチャーと同じくらい速いでしょう。

レベル3:-O3 -mfpu=neon -ftree-vectorize -ftree-vectorizer-verbose=1 -mfloat-abi=softfpでコンパイル +レギュラーGCC自動NEONベクトル化

void convertUVsp2UVp(
    unsigned char* __restrict srcptr, 
    unsigned char* __restrict dstptr, 
    int stride) 
{ 
    for(int i=0;i<stride;i++){ 
     dstptr[i]   = srcptr[i*2]; 
     dstptr[i + stride] = srcptr[i*2 + 1]; 
    } 
} 

、平均して約0.6ミリ秒を要します。参考のためにmemcpy640*480バイト、またはここでテストされた量の2倍の平均で約0.6ミリ秒かかる。

上記のNEONパラメータを使用してコンパイルされた2番目のコード(展開されていない点)は、ほぼ同じ時間0.6 msを要します。

関連する問題