いつmemcpy
を使用してパフォーマンスを向上させることができますか、それを使用することでどのようなメリットがありますか?例えば:
float a[3]; float b[3];
コードです:
memcpy(a, b, 3*sizeof(float));
速いこれよりも?
a[0] = b[0];
a[1] = b[1];
a[2] = b[2];
効率を気にする必要はありません。
クリーンで保守可能なコードを記述します。
非常に多くの回答が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 _a@GOTPCREL(%rip), %rcx
> movq _b@GOTPCREL(%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 _a@GOTPCREL(%rip), %rcx
> movq _b@GOTPCREL(%rip), %rdx
> movq (%rdx), %rax
> movq %rax, (%rcx)
> movl 8(%rdx), %eax
> movl %eax, 8(%rcx)
======= // std::copy()
10a11,14
> movq _a@GOTPCREL(%rip), %rsi
> movl $12, %edx
> movq _b@GOTPCREL(%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
memcpy
を使用できるのは、コピーするオブジェクトに明示的なコンストラクターがなく、そのメンバー(いわゆるPOD、「Plain Old Data」)がない場合のみです。したがって、memcpy
に対してfloat
を呼び出すことは問題ありませんが、たとえば、_std::string
_に対しては誤りです。
ただし、作業の一部はすでに行われています。_std::copy
_の_<algorithm>
_は、組み込み型(およびおそらく他のすべてのPOD型-STL実装に依存)に特化しています。したがって、std::copy(a, a + 3, b)
の記述はmemcpy
と同じくらい高速ですが(コンパイラの最適化後)、エラーが発生しにくくなります。
コンパイラは特にmemcpy
呼び出しを最適化しますが、少なくともclangとgccは最適化します。だからあなたはできる限りそれを好むべきです。
std::copy()
を使用します。 g++
のヘッダーファイルとして:
このインライン関数は、可能な限りmemmoveの呼び出しに要約されます。
おそらく、Visual Studioの場合とそれほど変わりません。通常の方法で進み、ボトルネックに気付いたら最適化します。単純なコピーの場合、コンパイラーはおそらくすでに最適化されています。
このようなmemcpyを使用するなど、時期尚早のマイクロ最適化を行わないでください。割り当てを使用すると、より明確でエラーが発生しにくくなり、適切なコンパイラを使用すると、適切に効率的なコードが生成されます。コードのプロファイルを作成し、割り当てが重大なボトルネックであることがわかった場合に限り、何らかのマイクロ最適化を検討できますが、一般的に、最初のインスタンスでは常に明確で堅牢なコードを記述する必要があります。
Memcpyのメリットは?おそらく読みやすさ。それ以外の場合は、多数の割り当てを行うか、コピー用のforループを作成する必要があります。どちらもmemcpyを実行するのと同じくらい単純で明確ではありません(もちろん、型が単純であり、構築を必要としない限り/破壊)。
また、memcpyは通常、特定のプラットフォーム向けに比較的最適化されており、単純な割り当てよりも遅くなることはなく、さらに高速になる場合もあります。
おそらく、Nawazが言ったように、割り当てバージョンはほとんどのプラットフォームで高速である必要があります。これは、2番目のバージョンでは一度に4バイトをコピーできるのに対し、memcpy()
はバイト単位でコピーするためです。
常にそうであるように、ボトルネックになると予想されるものが現実と一致することを確認するために、常にアプリケーションをプロファイルする必要があります。
編集
動的配列にも同じことが当てはまります。 C++について言及しているので、その場合はstd::copy()
アルゴリズムを使用する必要があります。
編集
これは、Windowsのコード出力ですXP GCC 4.5.0で、-O3フラグでコンパイルされます:
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バイト移動します。