在什么情况下我应该在C ++中使用memcpy而不是标准运算符?

时间:2010-12-28 08:50:38

标签: c++ performance memory allocation copying

我何时可以使用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];

7 个答案:

答案 0 :(得分:52)

效率不应该是您关注的问题 编写干净的可维护代码。

令我困扰的是,如此多的答案表明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

答案 1 :(得分:14)

只有当您复制的对象没有显式构造函数时,才可以使用memcpy,因为它们的成员(所谓的POD,“普通旧数据”)。因此,可以为memcpy调用float,但这对于std::string来说是错误的。

但部分工作已经完成了:来自std::copy的{​​{1}}专门用于内置类型(可能还用于其他所有POD类型 - 取决于STL实现)。因此,编写<algorithm>std::copy(a, a + 3, b)一样快(在编译器优化之后),但不容易出错。

答案 2 :(得分:10)

编译器专门优化memcpy次呼叫,至少是clang&amp; gcc呢。所以你应该在任何地方都喜欢它。

答案 3 :(得分:6)

使用std::copy()。作为g++注释的头文件:

  

这个内联函数将尽可能简单地调用@c memmove。

可能Visual Studio的差异并不大。以正常方式行走,并在您意识到瓶颈后进行优化。对于简单的副本,编译器可能已经为您进行了优化。

答案 4 :(得分:5)

不要过早使用微优化,例如使用memcpy。使用赋值更清晰,更不容易出错,任何体面的编译器都会生成适当高效的代码。如果且仅当您已经分析了代码并发现分配是一个重要的瓶颈,那么您可以考虑某种微优化,但一般情况下,您应该始终在第一个实例中编写清晰,健壮的代码。

答案 5 :(得分:4)

memcpy的好处?可能的可读性。否则,您将不得不进行多次分配或使用for循环进行复制,这两种方法都不像memcpy那样简单明了(当然,只要您的类型很简单且不需要构造/破坏)。

此外,memcpy通常针对特定平台进行相对优化,以至于它不会比简单分配慢得多,甚至可能更快。

答案 6 :(得分:0)

据说,正如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个字节。