When can I get better performance using memcpy
or how do I benefit from using it?
For example:
float a[3]; float b[3];
is code:
memcpy(a, b, 3*sizeof(float));
faster than this one?
a[0] = b[0];
a[1] = b[1];
a[2] = b[2];
When can I get better performance using memcpy
or how do I benefit from using it?
For example:
float a[3]; float b[3];
is code:
memcpy(a, b, 3*sizeof(float));
faster than this one?
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
from<algorithm>
专门用于内置类型(并且可能适用于所有其他 POD 类型 - 取决于 STL 实现)。所以编写std::copy(a, a + 3, b)
速度和 一样快(在编译器优化之后)memcpy
,但更不容易出错。
编译器专门优化memcpy
调用,至少 clang 和 gcc 是这样。所以你应该尽可能地喜欢它。
使用std::copy()
. 作为g++
注释的头文件:
这个内联函数将归结为尽可能调用@c memmove。
可能,Visual Studio 并没有太大的不同。采用正常方式,一旦发现瓶颈就进行优化。在简单副本的情况下,编译器可能已经在为您优化。
不要进行过早的微优化,例如像这样使用 memcpy。使用赋值更清晰,更不容易出错,任何体面的编译器都会生成适当高效的代码。当且仅当您已经对代码进行了概要分析并发现分配是一个重要的瓶颈,那么您可以考虑某种微优化,但通常您应该始终首先编写清晰、健壮的代码。
memcpy 的好处?大概是可读性。否则,您将不得不进行大量分配或使用 for 循环进行复制,这两者都不像执行 memcpy 那样简单明了(当然,只要您的类型简单且不需要构造/破坏)。
此外,memcpy 通常针对特定平台进行了相对优化,以至于它不会比简单分配慢很多,甚至可能更快。
据说,正如 Nawaz 所说,作业版本在大多数平台上应该更快。那是因为memcpy()
将逐字节复制,而第二个版本一次可以复制 4 个字节。
与往常一样,您应该始终对应用程序进行概要分析,以确保您期望的瓶颈与现实相符。
编辑
同样适用于动态数组。既然你提到了 C++,你应该std::copy()
在这种情况下使用算法。
编辑
这是带有 GCC 4.5.0 的 Windows XP 的代码输出,使用 -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 个字节。