2010-12-28 14 views
24

memcpyを使用してパフォーマンスを向上させるにはどうすればよいですか、それをどのように活用すればよいですか?たとえば :これより速いC++で標準の演算子よりもmemcpyを使うべきですか?

memcpy(a, b, 3*sizeof(float)); 

float a[3]; float b[3]; 

は、コードのですか?

a[0] = b[0]; 
a[1] = b[1]; 
a[2] = b[2]; 
+2

私は、floatの代入演算子もmemcpyを使用して実装されていると思います。だから、直接配列全体のmemcpyを使用すると速くなります – Akhil

+4

私はあなたの編集を信じていません。なぜ第2のアプローチがより速くなるのでしょうか? memcpy()は、メモリの領域をある場所から別の場所にコピーするように特別に設計されているため、基礎となるアーキテクチャが許す限り効率的でなければなりません。ブロックメモリコピーを実行するのに適切なアセンブリを使用することになるでしょう。 –

答えて

45

あなたの関心事ではありません。
メンテナンス可能なコードを書きます。

memcpy()が非効率的であることが非常に多くの回答からわかりました。これは、メモリのコピーブロックの最も効率的な方法(Cプログラム用)として設計されています。

だから私はテストとして次のことを書いた:

#include <algorithm> 

extern float a[3]; 
extern float b[3]; 
extern void base(); 

int main() 
{ 
    base(); 

#if defined(M1) 
    a[0] = b[0]; 
    a[1] = b[1]; 
    a[2] = b[2]; 
#elif defined(M2) 
    memcpy(a, b, 3*sizeof(float));  
#elif defined(M3) 
    std::copy(&a[0], &a[3], &b[0]); 
#endif 

    base(); 
} 

次にコードを比較して生成します。

g++ -O3 -S xr.cpp -o s0.s 
g++ -O3 -S xr.cpp -o s1.s -DM1 
g++ -O3 -S xr.cpp -o s2.s -DM2 
g++ -O3 -S xr.cpp -o s3.s -DM3 

echo "=======" > D 
diff s0.s s1.s >> D 
echo "=======" >> D 
diff s0.s s2.s >> D 
echo "=======" >> D 
diff s0.s s3.s >> D 

これがもたらした:(コメントは手作業で追加)

======= // Copy by hand 
10a11,18 
> movq [email protected](%rip), %rcx 
> movq [email protected](%rip), %rdx 
> movl (%rdx), %eax 
> movl %eax, (%rcx) 
> movl 4(%rdx), %eax 
> movl %eax, 4(%rcx) 
> movl 8(%rdx), %eax 
> movl %eax, 8(%rcx) 

======= // memcpy() 
10a11,16 
> movq [email protected](%rip), %rcx 
> movq [email protected](%rip), %rdx 
> movq (%rdx), %rax 
> movq %rax, (%rcx) 
> movl 8(%rdx), %eax 
> movl %eax, 8(%rcx) 

======= // std::copy() 
10a11,14 
> movq [email protected](%rip), %rsi 
> movl $12, %edx 
> movq [email protected](%rip), %rdi 
> call _memmove 

上記のループを実行するためのタイミング結果を1000000000に追加しました。

g++ -c -O3 -DM1 X.cpp 
    g++ -O3 X.o base.o -o m1 
    g++ -c -O3 -DM2 X.cpp 
    g++ -O3 X.o base.o -o m2 
    g++ -c -O3 -DM3 X.cpp 
    g++ -O3 X.o base.o -o m3 
    time ./m1 

    real 0m2.486s 
    user 0m2.478s 
    sys 0m0.005s 
    time ./m2 

    real 0m1.859s 
    user 0m1.853s 
    sys 0m0.004s 
    time ./m3 

    real 0m1.858s 
    user 0m1.851s 
    sys 0m0.006s 
+20

+1。そして、あなたがこれから明白な結論を書き留めていないので、memcpy呼び出しは最も効率的なコードを生成しているように見えます。 –

+1

違いはありますが、 '3 * sizeof(float)'は 'sizeof a 'でなければならないので、' a'のサイズが変わると 'memcpy'の呼び出しがそれに合わせて調整されます。 –

+0

Huh。なぜ '_memmove'の呼び出しがインライン化されていないのですか? –

10

コンパイラは、具体的にmemcpyコール、& GCCはない少なくとも打ち鳴らすを最適化します。あなたができるところならどこでもそれを好むべきです。

+0

@ismail:コンパイラは 'memcpy'を最適化するかもしれませんが、第2のアプローチよりも速くなる可能性は低いです。 Simoneの記事をお読みください。 – Nawaz

+1

@Nawaz:私は同意しない。 memcpy()は、アーキテクチャーのサポートにより、より高速になる可能性があります。とにかくこれはstd :: copy(@crazylammerで説明されているように)がおそらく最適な解決策であるため、冗長です。 –

0

おそらく、Nawaz氏によると、割り当てのバージョンは、ほとんどのプラットフォームでより速くなるはずです()。これは、memcpy()が1バイトずつコピーし、2番目のバージョンが4バイトずつコピーできるためです。

いつものように、ボトルネックと思われるものが現実と一致するようにアプリケーションを常にプロファイルする必要があります。


同じことがダイナミックアレイに適用されます。あなたはC++に言及しているので、その場合はstd::copy()アルゴリズムを使うべきです。

編集
これは-O3フラグを付けてコンパイルし、GCC 4.5.0でのWindows XP用のコードを出力します:

extern "C" void cpy(float* d, float* s, size_t n) 
{ 
    memcpy(d, s, sizeof(float)*n); 
} 

OPがあまりにも動的配列を指定したので、私はこの機能を行っています。

出力アセンブリは以下の通りです:もちろんの

_cpy: 
LFB393: 
    pushl %ebp 
LCFI0: 
    movl %esp, %ebp 
LCFI1: 
    pushl %edi 
LCFI2: 
    pushl %esi 
LCFI3: 
    movl 8(%ebp), %eax 
    movl 12(%ebp), %esi 
    movl 16(%ebp), %ecx 
    sall $2, %ecx 
    movl %eax, %edi 
    rep movsb 
    popl %esi 
LCFI4: 
    popl %edi 
LCFI5: 
    leave 
LCFI6: 
    ret 

、私はここでは何rep movsb手段を知っている専門家のすべてを想定しています。

extern "C" void cpy2(float* d, float* s, size_t n) 
{ 
    while (n > 0) { 
     d[n] = s[n]; 
     n--; 
    } 
} 

次のコードが得られる:

_cpy2: 
LFB394: 
    pushl %ebp 
LCFI7: 
    movl %esp, %ebp 
LCFI8: 
    pushl %ebx 
LCFI9: 
    movl 8(%ebp), %ebx 
    movl 12(%ebp), %ecx 
    movl 16(%ebp), %eax 
    testl %eax, %eax 
    je L2 
    .p2align 2,,3 
L5: 
    movl (%ecx,%eax,4), %edx 
    movl %edx, (%ebx,%eax,4) 
    decl %eax 
    jne L5 
L2: 
    popl %ebx 
LCFI10: 
    leave 
LCFI11: 
    ret 

一度に4つのバイトを移動

これは、割当のバージョンです。

+0

@Simone:最初のパラは私にとって意味があります。今私は確信が持てないので、確認する必要があります。 :-) – Nawaz

+7

私はmemcopyをバイト単位でコピーすると思いません。これは、特に大量のメモリを非常に効率的にコピーするように設計されています。 –

+0

ソースをお願いしますか? POSIXが義務づけているのは、[this](http://pubs.opengroup。org/onlinepubs/9699919799/functions/memcpy.html)。ところで、[この実装](http://www.gnu.org/software/mifluz/doc/doxydoc/memcpy2_8c-source.html)がそれほど速いかどうかを見てください。 – Simone

4

memcpyの利点は?おそらく可読性。それ以外の場合は、いくつかの割り当てを行うか、コピーのためのforループを持たなければならないでしょう。どちらもmemcpyを行うのと同じくらい簡単ではありません(もちろん、あなたの型がシンプルで、破壊)。

また、memcpyは、一般的に特定のプラットフォームに対して比較的単純化されていますが、単純な割り当てよりもはるかに遅くないこともあります。

14

memcpyは、コピーするオブジェクトに明示的なコンストラクタがない場合、つまりメンバー(いわゆるPain、 "Plain Old Data")の場合にのみ使用できます。だからの場合はmemcpyに電話しても構いませんが、たとえばstd::stringの場合は間違っています。

std::copyから<algorithm>までは、組み込みタイプに特化しています(そして、他のすべてのPODタイプでも可能ですが、STL実装に依存します)。したがって、std::copy(a, a + 3, b)を書くことは、memcpyのように(コンパイラの最適化後の)高速ですが、エラーが起こりにくくなります。

+7

'std :: copy'は' 'に正しくあります。 ''は厳密に下位互換性を保つためのものです。 –

4

このようにmemcpyを使用するなど、早すぎるマイクロ最適化は行ってはいけません。代入を使用すると、エラーが発生しにくくなり、適切なコンパイラが適切に効率的なコードを生成します。コードをプロファイリングして割り当てが重大なボトルネックになっている場合にのみ、ある種のマイクロ最適化を検討することができますが、一般的には最初のインスタンスで常に明確で堅牢なコードを記述する必要があります。

+1

N(N> 2)個の異なる配列項目を1つの 'memcpy'よりも1つずつ明確にする方法はありますか? 'memcpy(a、b、sizeof a)'は、 'a'と' b'のサイズが変更された場合、割り当てを追加/削除する必要がないので、より明確です。 –

+0

@Chris Lutz:コード全体の堅牢性については、生涯を通じて考える必要があります。ある時点で誰かがaの宣言を変更して配列の代わりにポインタになるとどうなりますか?この場合、割り当ては中断されませんでしたが、memcpyは失敗します。 –

+1

'memcpy'は壊れません(' sizeof aトリックは壊れますが、一部の人だけがそれを使います)。 'std :: copy'もどちらもほとんどすべての点で両者より優れていません。 –

6

std::copy()を使用してください。 g++のヘッダーファイルとして:

このインライン関数は、可能であれば@c memmoveの呼び出しに沸きます。

おそらく、Visual Studioの違いはあまりありません。通常の方法で行って、ボトルネックを認識したら最適化してください。単純なコピーの場合、コンパイラはおそらくすでにあなたのために最適化しています。

関連する問題