就性能而言,使用std :: memcpy()或std :: copy()更好吗?


163

它是更好地使用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]);

请注意,char可以签名或不签名,具体取决于实现方式。如果字节数可以大于等于128,则unsigned char用于您的字节数组。((int *)演员阵容也会更安全(unsigned int *)。)
Dan Breslau

13
为什么不使用std::vector<char>?或者,既然你说bitsstd::bitset
GManNickG 2011年

2
其实,您能告诉我什么(int*) copyMe->bits[0]吗?
user3728501 2015年

4
不知道为什么在几乎没有提供重要生命的情况下看起来如此混乱的东西是+81,但是嘿。@ user3728501我的猜测是缓冲区的开始int指示其大小,但这似乎是实现定义的灾难的秘诀,就像这里的许多其他事情一样。
underscore_d

2
实际上,该转换(int *)只是纯粹的未定义行为,而不是实现定义的行为。尝试通过强制转换进行类型修饰会违反严格的别名规则,因此标准完全未定义。(此外,在C ++中,尽管不是C,也不能通过union两者之一来输入双关。)几乎唯一的例外是,如果您要转换的变体char*,但容限不是对称的。
underscore_d

Answers:


206

在这里,我将违背通常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 *(其中T是用户实现中最大的类型,具有正确的溢出行为),因此可以快速访问我可以使用的最大类型是算法性能的核心,这些是我的结果:

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

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%

std :: copy超过memcpy的速度的平均平均总增加量:2.99%

我的编译器是Fedora 16 x86_64上的gcc 4.6.3。我的优化标志是-Ofast -march=native -funsafe-loop-optimizations

我的SHA-2实现的代码。

我决定也对我的MD5实现进行测试。结果不稳定得多,所以我决定进行10次运行。但是,经过最初的几次尝试后,我得到的结果从一次运行到下一次运行有很大的不同,所以我猜测正在发生某种OS活动。我决定重新开始。

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

这是我最后的10条结果:

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

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%

std :: copy速度超过memcpy的总平均降低速度:0.11%

我的MD5实施代码

这些结果表明在我的SHA-2测试中使用了std :: copy的一些优化,std::copy而在我的MD5测试中却没有使用。在SHA-2测试中,两个数组都是在与调用相同的函数中创建的std::copy /memcpy。在我的MD5测试中,其中一个数组作为函数参数传递给了函数。

我做了一些更多的测试,以了解如何做才能std::copy再次变得更快。答案很简单:打开链接时间优化。这些是我打开LTO的结果(gcc中的-flto选项):

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

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超过memcpy的速度的平均平均总增加量:0.72%

总而言之,使用似乎没有性能损失std::copy。实际上,似乎可以提高性能。

结果说明

那为什么会 std::copy提高性能呢?

首先,只要打开内联的优化功能,我就不会期望它对任何实现都会变慢。所有编译器都积极内联;它可能是最重要的优化,因为它可以实现许多其他优化。std::copy可以(并且我怀疑所有现实世界中的实现都可以)检测到这些参数是微不足道的可复制的,并且内存是按顺序排列的。这意味着在最坏的情况下(如果memcpy合法),它的std::copy性能也不会变差。琐碎的实现std::copy顺应了memcpy应满足“永远在线这样优化速度或大小时,”你的编译器的标准。

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

过早的优化?

std::copy处于有趣的位置。我希望它永远不会比memcpy任何现代优化编译器慢,有时甚至快。而且,只要有可能memcpy,就可以std::copymemcpy不允许在缓冲区中有任何重叠,而std::copy支持在一个方向上重叠(与std::copy_backward另一方向重叠)。memcpy只适用于指针,std::copy在任何迭代器的工作原理(std::mapstd::vectorstd::deque,或者我自己的自定义类型)。换句话说,只std::copy在需要复制数据块时使用。


35
我想强调的是,这并不意味着std::copymemcpy整个程序要执行快2.99%或0.72%或-0.11%。但是,我通常认为,真实代码中的基准比伪代码中的基准更有用。我的整个程序在执行速度上有了改变。两种复制方案的实际效果将具有比单独显示时此处所示更大的差异,但这表明它们在实际代码中可以有可测量的差异。
大卫·斯通

2
我不同意您的发现,但结果是结果:/。但是,有一个问题(我知道那是很久以前的事了,您不记得研究了,所以只以您的想法进行评论),您可能没有研究汇编代码。
ST3

2
在我看来memcpy,它std::copy具有不同的实现,因此在某些情况下,编译器会将周围的代码和实际的内存复制代码优化为一个完整的代码段。换句话说,有时候一个要比另一个好,甚至换句话说,决定使用哪个是过早的甚至是愚蠢的优化,因为在每种情况下,您都必须进行新的研究,而且,程序通常都在开发中,因此功能相对于其他功能的一些细微变化可能会丢失。
ST3

3
@ ST3:我想在最坏的情况下,std::copy是一个普通的内联函数,仅memcpy在合法时才调用。基本内联将消除任何负面的性能差异。我将用一些解释为什么std :: copy更快的方式来更新帖子。
大卫·斯通

7
非常有用的分析。Re std :: copy相对于memcpy的平均速度平均降低:0.11%,尽管数字正确,但结果在统计上并不显着。均值差异的95%置信区间为(-0.013s,0.025),其中包括零。正如您指出的那样,其他来源和数据存在差异,您可能会说性能是相同的。作为参考,其他两个结果在统计上都很显着-您看到这种极端情况的时间差大约为1亿分之一(第一个)和20,000万分之一(最后)。
TooTone

78

我知道的所有编译器都将在适当std::copymemcpy时候或更简单地将a替换为一个简单的向量,从而对副本进行矢量化,以使其比a更快。memcpy

无论如何:剖析并找出自己。不同的编译器将执行不同的操作,并且很可能不会完全按照您的要求执行。

参见此演示有关编译器优化(pdf)。

就是GCC对简单std::copy的POD类型所做的工作。

#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其本身将被大量矢量化。


1
我该如何进行剖析。使用什么工具(在Windows和Linux中)?
user576670 2011年

5
@Konrad,你是对的。但是memmove不应该更快-相反,它应该稍微慢一些,因为它必须考虑两个数据范围重叠的可能性。我认为std::copy允许重叠的数据,因此必须调用memmove
Charles Salvia

2
@Konrad:如果memmove总是比memcpy快,那么memcpy会称呼memmove。std :: copy实际上可能分派给的内容(如果有的话)是实现定义的,因此在不提及实现的情况下提及具体细节是没有用的。
Fred Nurk,2011年

1
虽然,有一个简单的程序可以重现此现象,但在GCC下用-O3编译时却显示了一个memcpy。这使我相信GCC会检查是否存在内存重叠。
jweyrich 2011年

1
@Konrad:标准std::copy允许在一个方向上重叠,但在另一个方向上不允许重叠。输出的开头不能在输入范围内,但允许输入的开头在输出范围内。这有点奇怪,因为定义了分配的顺序,即使定义了这些分配的效果,呼叫也可能是UB。但是我想该限制允许矢量化优化。
史蒂夫·杰索普

24

始终使用std::copy,因为memcpy仅限于C-POD风格结构,编译器可能会取代调用std::copymemcpy目标是否实际上POD。

另外,std::copy可以与许多迭代器类型一起使用,而不仅仅是指针。std::copy更灵活,不会造成性能损失,并且无疑是赢家。


您为什么要在迭代器周围进行复制?
Atmocreations 2011年

3
您不是要复制迭代器,而是要复制两个迭代器定义的范围。例如,std::copy(container.begin(), container.end(), destination);将内容复制container(之间的所有内容beginend)到缓冲区表示通过destinationstd::copy不需要像&*container.begin()或那样的恶作剧&container.back() + 1
大卫·斯通

16

从理论上讲,memcpy可能具有轻微的难以察觉的无限的性能优势,只是因为它与的要求不同std::copy。从手册页memcpy

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

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

因此,由于std::copy要求有所不同,因此从理论上讲应该稍微一些(特别强调一点),因为它可能会检查重叠的C数组,或者将C数组的复制委托给memmove,这需要执行C 检查。但是实际上,您(和大多数分析器)甚至都不会检测到任何差异。

当然,如果您不使用PODmemcpy无论如何都无法使用。


7
这是真的std::copy<char>。但是std::copy<int>可以假设其输入是int对齐的。这将带来更大的不同,因为它影响每个元素。重叠是一次性检查。
MSalters 2011年

2
@MSalters,是的,但是memcpy我看到的大多数实现都检查对齐并尝试复制单词,而不是逐字节复制。
Charles Salvia 2012年

1
std :: copy()也可以忽略重叠的内存。如果要支持重叠的内存,则必须编写自己的逻辑以在适当情况下调用std :: reverse_copy()。
Cygon 2012年

2
可以提出相反的论点:通过memcpy接口时,它将丢失对齐信息。因此,memcpy必须在运行时进行对齐检查,以处理未对齐的开始和结束。这些支票可能很便宜,但不是免费的。而std::copy可以避免这些检查并进行矢量化。同样,编译器可以证明源数组和目标数组不重叠,并且可以再次向量化,而无需用户在memcpy和之间进行选择memmove
Maxim Egorushkin 2016年

11

我的规则很简单。如果您使用的是C ++,请选择C ++库而不是C :)


40
C ++被明确设计为允许使用C库。这不是偶然的。通常,在C ++中使用std :: copy比使用memcpy更好,但这与C是无关的,并且这种参数通常是错误的方法。
Fred Nurk,2011年

2
@FredNurk通常您想避免C的薄弱区域,因为C ++提供了更安全的替代方法。
Phil1970年

@ Phil1970我不确定在这种情况下C ++是否更安全。我们仍然必须传递不会超载的有效迭代器,等等。我能够使用std::end(c_arr)代替而不是c_arr + i_hope_this_is_the_right_number_of elements更安全?更重要的是,更清晰。这就是我在此特定情况下强调的要点:std::copy()如果迭代器的类型稍后更改,则更惯用,更易于维护,导致语法更清晰,等等
underscore_d

1
@underscore_d std::copy更安全,因为如果它们不是POD类型,它将正确地复制传递的数据。memcpy会很高兴地将一个std::string字节的对象复制到一个新的表示形式中。
詹斯

3

只是一个小小的补充:memcpy()和之间的速度差异std::copy()可能会有所不同,具体取决于是否启用了优化。如果使用g ++ 6.2.0,并且没有进行优化,则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 memcpy()如果未启用优化快两倍。

源代码(需要Google Benchmark):

#include <string.h>
#include <algorithm>
#include <vector>
#include <benchmark/benchmark.h>

constexpr int N = 10;

void bm_memcpy(benchmark::State& state)
{
  std::vector<int> a(N);
  std::vector<int> r(N);

  while (state.KeepRunning())
  {
    memcpy(r.data(), a.data(), N * sizeof(int));
  }
}

void bm_stdcopy(benchmark::State& state)
{
  std::vector<int> a(N);
  std::vector<int> r(N);

  while (state.KeepRunning())
  {
    std::copy(a.begin(), a.end(), r.begin());
  }
}

void bm_stdcopy_n(benchmark::State& state)
{
  std::vector<int> a(N);
  std::vector<int> r(N);

  while (state.KeepRunning())
  {
    std::copy_n(a.begin(), N, r.begin());
  }
}

BENCHMARK(bm_memcpy);
BENCHMARK(bm_stdcopy);
BENCHMARK(bm_stdcopy_n);

BENCHMARK_MAIN()

/* EOF */

18
在禁用优化的情况下衡量性能是...好...几乎没有意义...如果您对性能感兴趣,那么没有优化就无法编译。
bolov '16

3
@bolov并非总是如此。在某些情况下,拥有一个相对较快的调试程序很重要。
橡子

2

如果确实需要最大的复制性能(可能不需要),则不要使用它们

很多可以做,以优化内存复制-甚至更多,如果你愿意使用多线程吧/内核。参见,例如:

此memcpy实施中缺少什么/欠佳?

问题和一些答案都建议了实现方式或实现方式的链接。


4
pedant模式:通常的警告是“不使用它们 ”指的是如果您已经证明自己有一个高度特定的情况/要求,而您的实现所提供的Standard函数都不够快;否则,我通常会担心的是,尚未证明自己的人会过早地过早地优化复制代码,而不是程序中通常更有用的部分。
underscore_d

-2

分析显示以下语句:std::copy()始终与memcpy()错误一样快或更快。

我的系统:

HP-Compaq-dx7500-Microtower 3.13.0-24-generic#47-Ubuntu SMP Fri May 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:现在:1422969084:04859已用:2650 us
std :: copy()profile:main:27:现在:1422969084:04862 elapsed:2745 us
memcpy()使用了44 s std :: copy( )过去45秒

g ++ -O3 -o test_stdcopy test_stdcopy.cpp

memcpy()配置文件:main:21:现在:1422969601:04939过去了:2385 us
std :: copy()profile:main:28:现在:1422969601:04941 elapsed:2690 us
memcpy()已经过去27 s std :: copy( )过去了43秒

Red Alert指出,代码使用从数组到数组的memcpy和从数组到矢量的std :: copy。这是更快进行mcmcpy的原因。

既然有

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 

1
错,您的剖析表明复制到数组比复制到向量要快。题外话。
红色警戒

我可能是错的,但是在正确的示例中,使用memcpy时,不是将arr2复制到arr1中,而使用std :: copy时,是将arr1复制到arr2中吗?...您可以做的是使多个交替实验(一次执行memcpy,一次执行std :: copy,然后再次使用memcopy等进行多次)。然后,我将使用clock()而不是time(),因为谁会知道您除了该程序外PC还能做什么。不过,只有我的两分钱... :-)
paercebal 2015年

7
那么,std::copy以某种方式从向量切换到数组所需的时间memcpy几乎是原来的两倍?此数据高度可疑。我使用带有-O3的gcc编译了您的代码,并且两个循环的生成程序集相同。因此,您在机器上观察到的任何时间差异只是偶然的。
红色警戒
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.