在性能方面使用std :: memcpy()或std :: copy()会更好吗?

时间:2011-01-16 17:55:25

标签: c++ performance optimization

如下所示使用memcpy是否更好?或者在性能方面使用std::copy()会更好吗?为什么呢?

char *bits = NULL;
...

bits = new (std::nothrow) char[((int *) copyMe->bits)[0]];
if (bits == NULL)
{
    cout << "ERROR Not enough memory.\n";
    exit(1);
}

memcpy (bits, copyMe->bits, ((int *) copyMe->bits)[0]);

8 个答案:

答案 0 :(得分:178)

我将违背一般智慧,std::copy会有轻微的,几乎难以察觉的性能损失。我刚做了一个测试,发现这是不真实的:我确实注意到了性能差异。但是,获胜者是std::copy

我编写了一个C ++ SHA-2实现。在我的测试中,我使用所有四个SHA-2版本(224,256,384,512)散列5个字符串,并且我循环300次。我使用Boost.timer测量时间。 300循环计数器足以完全稳定我的结果。我分别运行了5次测试,在memcpy版本和std::copy版本之间交替进行。我的代码利用尽可能大的块来抓取数据(许多其他实现使用char / char *,而我使用T / T *(其中{ {1}}是用户实现中具有正确溢出行为的最大类型),因此对我所能使用的最大类型的快速内存访问是我算法性能的核心。这些是我的结果:

完成SHA-2测试运行的时间(以秒为单位)

T

std :: copy over memcpy的平均速度增加:2.99%

我的编译器是Fedora 16 x86_64上的gcc 4.6.3。我的优化标记为std::copy memcpy % increase 6.11 6.29 2.86% 6.09 6.28 3.03% 6.10 6.29 3.02% 6.08 6.27 3.03% 6.08 6.27 3.03%

Code for my SHA-2 implementations.

我决定对我的MD5实施进行测试。结果不太稳定,所以我决定进行10次运行。然而,在我的第一次尝试之后,我得到的结果在一次运行到另一次运行之间变化很大,所以我猜测正在进行某种操作系统活动。我决定重新开始。

相同的编译器设置和标志。只有一个版本的MD5,它比SHA-2更快,所以我在一组类似的5个测试字符串上做了3000个循环。

这是我最后的10个结果:

完成MD5测试运行的时间(以秒为单位)

-Ofast -march=native -funsafe-loop-optimizations

std :: copy over memcpy的平均速度下降:0.11%

Code for my MD5 implementation

这些结果表明我的SHA-2测试中使用了std :: copy的一些优化std::copy memcpy % difference 5.52 5.56 +0.72% 5.56 5.55 -0.18% 5.57 5.53 -0.72% 5.57 5.52 -0.91% 5.56 5.57 +0.18% 5.56 5.57 +0.18% 5.56 5.53 -0.54% 5.53 5.57 +0.72% 5.59 5.57 -0.36% 5.57 5.56 -0.18% 在我的MD5测试中无法使用。在SHA-2测试中,两个数组都是在调用std::copy / std::copy的同一函数中创建的。在我的MD5测试中,其中一个数组作为函数参数传递给函数。

我做了一些测试,看看我能做些什么才能让memcpy再次更快。答案结果很简单:打开链接时间优化。这些是我打开LTO的结果(选项-flto在gcc中):

使用-flto完成MD5测试运行的时间(以秒为单位)

std::copy

std :: copy over memcpy的平均增长率:0.72%

总之,使用std::copy memcpy % difference 5.54 5.57 +0.54% 5.50 5.53 +0.54% 5.54 5.58 +0.72% 5.50 5.57 +1.26% 5.54 5.58 +0.72% 5.54 5.57 +0.54% 5.54 5.56 +0.36% 5.54 5.58 +0.72% 5.51 5.58 +1.25% 5.54 5.57 +0.54% 似乎没有性能损失。事实上,似乎有性能提升。

结果说明

那么为什么std::copy可以提升性能呢?

首先,我认为只要打开内联优化,任何实现都不会慢。所有编译器都积极地内联;它可能是最重要的优化,因为它可以实现许多其他优化。 std::copy可以(并且我怀疑所有现实世界的实现都可以)检测到参数是可以轻易复制的,并且内存是按顺序布局的。这意味着在最糟糕的情况下,当std::copy合法时,memcpy应该不会更糟。 std::copy遵循std::copy的简单实现应符合编译器的标准“在优化速度或大小时始终内联”。

但是,memcpy还会保留更多信息。当您调用std::copy时,该函数会保持类型不变。 std::copymemcpy上运行,它会丢弃几乎所有有用的信息。例如,如果我传入一个void *数组,编译器或库实现者可能能够利用std::uint64_t的64位对齐,但使用{可能更难做到{1}}。像这样的算法的许多实现通过首先处理范围开始处的未对准部分,然后是对齐部分,然后是末端处的未对准部分来工作。如果保证所有对齐,则代码变得更简单,更快速,并且处理器中的分支预测器更容易正确。

过早优化?

std::copy处于一个有趣的位置。我希望它永远不会慢于memcpy,有时候使用任何现代优化编译器都会更快。此外,您可以std::copy进行任何操作,memcpymemcpy不允许缓冲区中的任何重叠,而std::copy支持在一个方向上重叠(对于另一个重叠方向,memcpy支持重叠)。 std::copy仅适用于指针,std::copy_backward适用于任何迭代器(memcpystd::copystd::map或我自己的自定义类型)。换句话说,当您需要复制数据块时,您应该只使用std::vector

答案 1 :(得分:77)

我知道的所有编译器都会在适当的时候用std::copy替换一个简单的memcpy,或者更好的是,将副本矢量化,使其比memcpy更快。

无论如何:简介并找出自己。不同的编译器会做不同的事情,很可能它不会完全按你的要求做。

this presentation on compiler optimisations(pdf)。

对于POD类型的简单std::copy,此处为what GCC does

#include <algorithm>

struct foo
{
  int x, y;    
};

void bar(foo* a, foo* b, size_t n)
{
  std::copy(a, a + n, b);
}

以下是反汇编(仅-O优化),显示对memmove的调用:

bar(foo*, foo*, unsigned long):
    salq    $3, %rdx
    sarq    $3, %rdx
    testq   %rdx, %rdx
    je  .L5
    subq    $8, %rsp
    movq    %rsi, %rax
    salq    $3, %rdx
    movq    %rdi, %rsi
    movq    %rax, %rdi
    call    memmove
    addq    $8, %rsp
.L5:
    rep
    ret

如果将功能签名更改为

void bar(foo* __restrict a, foo* __restrict b, size_t n)

然后memmove成为memcpy,以提高性能。请注意,memcpy本身将被大量矢量化。

答案 2 :(得分:23)

始终使用std::copy,因为memcpy仅限于C风格的POD结构,如果目标位于std::copy,编译器可能会将memcpy的调用替换为std::copy事实上POD。

另外,std::copy可以与许多迭代器类型一起使用,而不仅仅是指针。 {{1}}更灵活,没有性能损失,是明显的赢家。

答案 3 :(得分:17)

理论上,memcpy可能会有轻微的难以察觉的无穷小,性能优势,只是因为它没有与std::copy具有相同的要求。来自memcpy的手册页:

  

避免溢出,大小   目标指向的数组   和源参数,应为   至少num个字节,,不应该   重叠(用于重叠内存   块,memmove是一种更安全的方法。)

换句话说,memcpy可以忽略数据重叠的可能性。 (将重叠数组传递给memcpy是未定义的行为。)因此memcpy不需要显式检查此条件,而只要std::copy可以使用OutputIterator参数不在源范围内。请注意,与说明源范围和目标范围不能重叠相同。

因为std::copy有一些不同的要求,理论上它应该(特别强调)慢,因为它可能会检查对于重叠的C数组,或者将C数组的复制委托给需要执行检查的memmove。但在实践中,你(和大多数剖析器)可能甚至都不会发现任何差异。

当然,如果您不使用PODs,则无论如何都

答案 4 :(得分:9)

我的规则很简单。如果你使用C ++更喜欢C ++库而不是C:)

答案 5 :(得分:3)

只是一个小小的补充:{ path: '', redirectTo: '/admin', pathMatch: 'full' }memcpy()之间的速度差异可能会有很大差异,具体取决于是启用还是禁用优化。使用g ++ 6.2.0并且没有优化std::copy()明显胜出:

memcpy()

当启用优化(Benchmark Time CPU Iterations --------------------------------------------------- bm_memcpy 17 ns 17 ns 40867738 bm_stdcopy 62 ns 62 ns 11176219 bm_stdcopy_n 72 ns 72 ns 9481749 )时,一切看起来几乎相同:

-O3

数组越大,效果越不明显,但即使在Benchmark Time CPU Iterations --------------------------------------------------- bm_memcpy 3 ns 3 ns 274527617 bm_stdcopy 3 ns 3 ns 272663990 bm_stdcopy_n 3 ns 3 ns 274732792 N=1000时,如果未启用优化,速度也会快两倍。

源代码(需要Google Benchmark):

memcpy()

答案 6 :(得分:2)

如果你真的需要最大的复制性能(你可能没有),不使用它们

可以使用 lot 来优化内存复制 - 如果你愿意为它使用多个线程/核心,那就更多了。例如,见:

What's missing/sub-optimal in this memcpy implementation?

问题和一些答案都提出了实施或实施链接。

答案 7 :(得分:-2)

分析显示该语句:std::copy()始终与memcpy()一样快,或者更快为假。

我的系统:

  

HP-Compaq-dx7500-Microtower 3.13.0-24-generic#47-Ubuntu SMP Fri 5月2日   23:30:00 UTC 2014 x86_64 x86_64 x86_64 GNU / Linux。

     

gcc(Ubuntu 4.8.2-19ubuntu1)4.8.2

代码(语言:c ++):

    const uint32_t arr_size = (1080 * 720 * 3); //HD image in rgb24
    const uint32_t iterations = 100000;
    uint8_t arr1[arr_size];
    uint8_t arr2[arr_size];
    std::vector<uint8_t> v;

    main(){
        {
            DPROFILE;
            memcpy(arr1, arr2, sizeof(arr1));
            printf("memcpy()\n");
        }

        v.reserve(sizeof(arr1));
        {
            DPROFILE;
            std::copy(arr1, arr1 + sizeof(arr1), v.begin());
            printf("std::copy()\n");
        }

        {
            time_t t = time(NULL);
            for(uint32_t i = 0; i < iterations; ++i)
                memcpy(arr1, arr2, sizeof(arr1));
            printf("memcpy()    elapsed %d s\n", time(NULL) - t);
        }

        {
            time_t t = time(NULL);
            for(uint32_t i = 0; i < iterations; ++i)
                std::copy(arr1, arr1 + sizeof(arr1), v.begin());
            printf("std::copy() elapsed %d s\n", time(NULL) - t);
        }
    }
  

g ++ -O0 -o test_stdcopy test_stdcopy.cpp

     

memcpy()个人资料:main:21:now:1422969084:04859已过:2650 us   std :: copy()profile:main:27:now:1422969084:04862逝去:2745 us
  memcpy()过去44 s std :: copy()过了45 s

     

g ++ -O3 -o test_stdcopy test_stdcopy.cpp

     

memcpy()个人资料:main:21:now:1422969601:04939已过:2385我们
  std :: copy()profile:main:28:now:1422969601:04941逝去:2690 us
  memcpy()过了27 s std :: copy()过了43 s

Red Alert指出代码使用memcpy从数组到数组,std :: copy从数组到vector。这可能是更快记忆的原因。

因为有

v.reserve(的sizeof(ARR1));

复制到矢量或数组没有区别。

代码被修复为两种情况都使用数组。 memcpy仍然更快:

{
    time_t t = time(NULL);
    for(uint32_t i = 0; i < iterations; ++i)
        memcpy(arr1, arr2, sizeof(arr1));
    printf("memcpy()    elapsed %ld s\n", time(NULL) - t);
}

{
    time_t t = time(NULL);
    for(uint32_t i = 0; i < iterations; ++i)
        std::copy(arr1, arr1 + sizeof(arr1), arr2);
    printf("std::copy() elapsed %ld s\n", time(NULL) - t);
}

memcpy()    elapsed 44 s
std::copy() elapsed 48 s