什么是C++中最快的可移植方法来复制一个数组?

21

这个问题困扰我已经有一段时间了。我正在考虑的选择有:

  1. memcpy
  2. std::copy
  3. cblas_dcopy

有人知道这三种方法的优缺点吗?如果还有其他建议也欢迎提供。

8个回答

30

在C++中,除非有充分的理由,否则默认应使用std::copy。原因是C++类通过复制构造函数和复制赋值运算符定义了它们自己的复制语义,而在列出的操作中,只有std::copy遵守这些约定。

memcpy()使用原始的、字节级别的数据复制(虽然很可能已经针对缓存行大小等进行了优化),并忽略了C++的复制语义(毕竟它是一个C函数...)。

cblas_dcopy()是一个专门用于使用双精度浮点值的线性代数例程的特殊函数。它可能在这方面表现出色,但不应被视为通用型。

如果您的数据是“简单”的POD类型结构数据或原始基本类型数据,那么memcpy很可能是您可以得到的最快速的方法。同样,std::copy在这些情况下将被优化为使用memcpy,因此您永远不会知道区别。

总之,使用std::copy()。


似乎std::copy更倾向于使用std::memmove,因为允许范围在一端重叠。 - visitor
@visitor:很可能是真的。但我敢打赌,如果它确定范围不重叠(简单的指针算术),memmove()会调用memcpy()。 - Drew Hall
我看过一个memmove的实现,如果重叠会导致向前复制出问题,它只是将复制反向进行。 - doron
此外,std::copy(至少在理论上)可以利用特定平台的优化和/或特定类型。--- @deus-ex-machina399:这是典型的解决方案,但向后复制不是缓存最优的。 - peterchen

2
使用std::copy,除非性能分析表明使用其他方法可以获得更好的效果。它遵循C ++对象封装,调用复制构造函数和赋值运算符,并且实现可能包括其他内联优化。如果要复制的类型从可平凡复制的类型更改为不可平凡复制的类型,则这种方法更易于维护。
正如PeterCordes在下面评论的那样,现代编译器(如GCC和clang)在内部分析memcpy()请求,并通常避免使用单独的函数调用,即使在此之前,一些系统也有memcpy()宏,可以将大小低于某个阈值的复制内联。
就我手头上的旧Linux盒子而言(2010年),GCC没有进行任何惊人的优化,但是bits/type_traits.h允许程序轻松指定std::copy是否应该转到memcpy()(请参见下面的代码),因此没有理由使用memcpy()直接替换std::copy()
 * Copyright (c) 1997
 * Silicon Graphics Computer Systems, Inc.
 *
 * Permission to use, copy, modify, distribute and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and            
 * that both that copyright notice and this permission notice appear            
 * in supporting documentation.  Silicon Graphics makes no                      
 * representations about the suitability of this software for any               
 * purpose.  It is provided "as is" without express or implied warranty.        
 ...                                                                            
                                                                            
/*                                                                              
This header file provides a framework for allowing compile time dispatch        
based on type attributes. This is useful when writing template code.            
For example, when making a copy of an array of an unknown type, it helps        
to know if the type has a trivial copy constructor or not, to help decide       
if a memcpy can be used.

The class template __type_traits provides a series of typedefs each of
which is either __true_type or __false_type. The argument to
__type_traits can be any type. The typedefs within this template will
attain their correct values by one of these means:
    1. The general instantiation contain conservative values which work
       for all types.
    2. Specializations may be declared to make distinctions between types.
    3. Some compilers (such as the Silicon Graphics N32 and N64 compilers)
       will automatically provide the appropriate specializations for all
       types.

EXAMPLE:

//Copy an array of elements which have non-trivial copy constructors
template <class _Tp> void
  copy(_Tp* __source,_Tp* __destination,int __n,__false_type);
//Copy an array of elements which have trivial copy constructors. Use memcpy.
template <class _Tp> void
  copy(_Tp* __source,_Tp* __destination,int __n,__true_type);

//Copy an array of any type by using the most efficient copy mechanism
template <class _Tp> inline void copy(_Tp* __source,_Tp* __destination,int __n) {
   copy(__source,__destination,__n,
        typename __type_traits<_Tp>::has_trivial_copy_constructor());
}
*/

现代gcc/clang内联小型固定大小的memcpy。它们将memcpy视为内置函数/内部函数,有点像=运算符:一种扩展它的方法是调用memcpy库函数,但同样有效的是将其编译为负载指令(到寄存器)如果可以优化掉或者编译成寄存器。(例如,对于类型转换float <=> int和一个小的4字节memcpy,您通常会从x86-64的clang中获得movd xmm0,eax,只是寄存器之间的移动。) - Peter Cordes
@PeterCordes:哦,是的,这可能需要更新 - 谢谢。此外,C++20引入了std::bit_cast - 这可能会包装一个对memcpy的调用 - 作为一种安全地执行您所描述的类型转换的方式。 - Tony Delroy

1

在大多数情况下,memcpy将是最快的,因为它是最低级别的,并且可以在给定平台上以机器代码实现。(但是,如果您的数组包含非平凡对象,则memcpy可能无法正确处理,因此坚持使用std::copy可能更安全)

然而,这完全取决于stdlib在给定平台上的实现等。由于标准没有规定操作必须有多快,因此在“可移植”中无法知道哪种方法最快。

对应用程序进行分析将显示在给定平台上最快的方法,但只会告诉您有关测试平台的信息。

然而,当您对应用程序进行分析时,您很可能会发现问题在于您的设计,而不是您选择的数组复制方法。(例如,为什么需要复制如此大的数组?)


1

memcpy,但是如果您的数组包含非平凡对象,请使用std::copy


3
良好实现的std::copy即使对于基本对象也可以更快;而memcpy需要处理任意地址对齐,但std::copy在编译时就知道了对齐方式。 - Mike Seymour
5
很多C++性能优化技巧似乎都包括“一个好的实现……可能会更快”的限定词。这些假设性的优化有多少曾经在任何地方被实现过? 有多少这样的假设性优化实际上已经被实现过,在任何地方? - Porculus
1
@Viktor:是的。在我的GCC版本中,对POD数据数组使用std::copy会生成一个调用memmove的函数。任何好的编译器都会这样做(或者为了加分,调用一个专门针对数据类型对齐的字节复制函数),因此没有理由为了认为它可能更快而牺牲类型安全性而调用memcpy - Mike Seymour
@Mike Seymour:您的观点是,通过引用编译器使用memmove,它将不会变慢,而这比memcpy慢(即使只是稍微慢一点)?我不明白您所说的利用对齐方式,您指的是优化复制大块内存时会做什么? - Viktor Sehr
@Viktor:好的,速度没有显著变慢(可能还稍微快了一点);很抱歉我没有表达得更加精确。 - Mike Seymour
显示剩余6条评论

1

memcpy 可能是复制连续内存块最快的方法。这是因为它很可能被高度优化到特定的硬件位上。通常情况下,它被实现为内置编译器函数。

话虽如此,非 POD C++ 对象不太可能是连续的,因此使用 memcpy 复制 C++ 对象数组可能会导致意外结果。当复制 C++ 对象数组(或集合)时,std::copy 将使用对象自己的复制语义,因此适用于非 POD C++ 对象。

cblas_dcopy 看起来是用于特定库的复制,当不使用该库时,它可能没有多少用处。


为什么您认为std::copymemcpy慢? - jalf

0

我认为其他人会调用memcpy()。话虽如此,我不相信会有任何明显的差异。

如果这真的很重要,那就编写所有三个版本并运行分析器,但最好考虑可读性/可维护性、异常安全等因素...(在此同时编写汇编插入代码,尽管你可能看不到任何区别)

你的程序是多线程的吗?

最重要的是,你是如何声明数组的?(它是什么类型的数组)它有多大?


0
我进行了一个小型基准测试(使用VS 2018预览版,MKL 2017更新4),比较了memcpycblas_?copy的顺序版本,并发现它们在floatdouble上的速度相同。

-3

只需对您的应用程序进行分析。您很可能会发现复制并不是它最慢的部分。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接