strncpy / memcpy / memmove是逐字节还是以另一种有效方式复制数据?

时间:2019-01-22 10:48:50

标签: memcpy strcpy strncpy memmove effective-c++

我们知道,在多字节字计算机(例如x86 / x86_64)中,逐字节地复制/移动大量内存(每步4或8个字节)要比逐字节复制/移动更为有效。按字节。

我很好奇strncpy / memcpy / memmove用哪种方式做事, 以及它们如何处理记忆字对齐。

char buf_A[8], buf_B[8];

// I often want to code as this
*(double*)buf_A = *(double*)buf_B;

//in stead of this
strcpy(buf_A, buf_B);
// but it worsen the readability of my codes.

6 个答案:

答案 0 :(得分:3)

通常,您不必考虑如何实现memcpy或其他类似功能。除非您的分析证明您是错误的,否则您应该假设它们是有效的。

实际上,它确实进行了优化。参见例如以下测试代码:

#include <cstring>

void test(char (&a)[8], char (&b)[8])
{
    std::memcpy(&a,&b,sizeof a);
}

使用命令g++ test.cpp -O3 -S -masm=intel用g ++ 7.3.0对其进行编译,我们can see使用以下汇编代码:

test(char (&) [8], char (&) [8]):

    mov     rax, QWORD PTR [rsi]
    mov     QWORD PTR [rdi], rax
    ret

如您所见,副本不仅是内联的,而且还折叠为一个8字节的单个读写。

答案 1 :(得分:2)

在这种情况下,您可能更喜欢使用memcpy,因为它等效于*(double*)buf_A = *(double*)buf_B;而没有未定义的行为。

您不必担心调用memcpy,因为默认情况下,编译器假定对memcpy的调用具有c库中定义的含义。因此,根据参数的类型和/或在编译时对副本大小的了解,编译器可以选择不调用c库函数,而是内联更适合的内存复制策略。在gcc上,您可以使用-fno-builtin编译器选项demo禁用此行为。

需要由编译器替换memcpy调用,因为memcpy将检查指针的大小和对齐方式,以使用最有效的内存复制策略(它可能会开始使用char逐个char地复制到很小的块,而使用以AVX512指令为例)。这些检查以及对memcpy的任何调用都会造成费用。

此外,如果您正在寻找效率,则应该关注内存对齐。因此,您可能需要声明缓冲区的对齐方式:

alignas(8) char buf_A[8];

答案 2 :(得分:1)

来自cpp-reference

  

从src指向的对象到dest指向的对象的副本计数字节。这两个对象都被重新解释为无符号字符数组。

     

注释

     

std :: memcpy是用于内存到内存复制的最快的库例程。通常它比std :: strcpy效率更高,后者必须扫描其复制的数据或std :: memmove,后者必须采取预防措施来处理重叠的输入。

     

几个C ++编译器将合适的内存复制循环转换为std :: memcpy调用。

     

如果严格的别名禁止检查与两种不同类型的值相同的内存,则可以使用std :: memcpy转换值。

因此,它应该是复制数据的最快方法。但是请注意,在某些情况下,行为是不确定的:

  

如果对象重叠,则行为是不确定的。

     

如果dest或src是空指针,则即使count为零,行为也不确定。

     

如果对象可能重叠或不可TriviallyCopyable,则未指定memcpy的行为,并且可能未定义。

答案 3 :(得分:1)

  

strcpy / strncpy是否逐字节或以其他有效方式复制数据?

C ++或C标准都没有指定strcpy / strncpy的实现方式。他们只描述行为。

有多种标准库实现,每种实现都选择如何实现其功能。可以使用memcpy来实现这两个。这些标准也没有完全描述memcpy的实现,并且多个实现的存在也适用于它。

memcpy可以利用完整单词复制来实现。关于如何实现 的简短伪代码:

if len >= 2 * word size
    copy bytes until destination pointer is aligned to word boundary
    if len >= page size
        copy entire pages using virtual address manipulation
    copy entire words
 copy the trailing bytes that are not aligned to word boundary

要了解特定标准库实现是如何实现strcpy / strncpy / memcpy的,可以阅读标准库的源代码-如果可以访问它。

甚至,当在编译时知道长度时,编译器甚至可能选择不使用库memcpy,而是内联复制。不管您的编译器是否内置了标准库函数的定义,您都可以在相应编译器的文档中找到。

答案 4 :(得分:1)

这取决于您使用的编译器和您使用的C运行时库。在大多数情况下,string.h函数(例如memcmpmemcpystrcpumemset等)都是通过汇编以CPU优化的方式实现的。

您可以找到这些功能for the AMD64 arhitecture的GNU libc实现。如您所见,它可能会使用SSE或AVX指令在每次迭代中复制128位和512位。微软还将CRT的源代码与Visual Studio捆绑在一起(大多数方法相同,支持MMX,SSE,AVX循环)。

编译器还对此类函数进行了特殊的优化,GCC将其称为builtins,其他编译器将其称为固有函数。即编译器可以选择-调用库函数,或生成适合于当前上下文的CPU特定汇编代码。例如,当N的{​​{1}}参数是常量时,即memcpy编译器可以生成内联汇编代码(类似于memcpy(dst, src, 128)),而当它是变量时,即{{1 }}-编译器可能会插入对库函数的调用(类似mov 16,rcx cls rep stosq

答案 5 :(得分:0)

我认为此页面上的所有观点和建议都是合理的,但我决定尝试一下实验。

令我惊讶的是,最快的方法不是我们理论上期望的方法。

我尝试了一些如下代码。

#include <cstring>
#include <iostream>
#include <string>
#include <chrono>

using std::string;
using std::chrono::system_clock;

inline void mycopy( double* a, double* b, size_t s ) {
   while ( s > 0 ) {
      *a++ = *b++;
      --s;
   }
};

// to make sure that every bits have been changed
bool assertAllTrue( unsigned char* a, size_t s ) {
   unsigned char v = 0xFF;
   while ( s > 0 ) {
      v &= *a++;
      --s;
   }
   return v == 0xFF;
};

int main( int argc, char** argv ) {
   alignas( 16 ) char bufA[512], bufB[512];
   memset( bufB, 0xFF, 512 );  // to prevent strncpy from stoping prematurely
   system_clock::time_point startT;

   memset( bufA, 0, sizeof( bufA ) );
   startT = system_clock::now();
   for ( int i = 0; i < 1024 * 1024; ++i )
      strncpy( bufA, bufB, sizeof( bufA ) );
   std::cout << "strncpy:" << ( system_clock::now() - startT ).count()
             << ", AllTrue:" << std::boolalpha
             << assertAllTrue( ( unsigned char* )bufA, sizeof( bufA ) )
             << std::endl;

   memset( bufA, 0, sizeof( bufA ) );
   startT = system_clock::now();
   for ( int i = 0; i < 1024 * 1024; ++i )
      memcpy( bufA, bufB, sizeof( bufA ) );
   std::cout << "memcpy:" << ( system_clock::now() - startT ).count()
             << ", AllTrue:" << std::boolalpha
             << assertAllTrue( ( unsigned char* )bufA, sizeof( bufA ) )
             << std::endl;

   memset( bufA, 0, sizeof( bufA ) );
   startT = system_clock::now();
   for ( int i = 0; i < 1024 * 1024; ++i )
      memmove( bufA, bufB, sizeof( bufA ) );
   std::cout << "memmove:" << ( system_clock::now() - startT ).count()
             << ", AllTrue:" << std::boolalpha
             << assertAllTrue( ( unsigned char* )bufA, sizeof( bufA ) )
             << std::endl;

   memset( bufA, 0, sizeof( bufA ) );
   startT = system_clock::now();
   for ( int i = 0; i < 1024 * 1024; ++i )
      mycopy( ( double* )bufA, ( double* )bufB, sizeof( bufA ) / sizeof( double ) );
   std::cout << "mycopy:" << ( system_clock::now() - startT ).count()
             << ", AllTrue:" << std::boolalpha
             << assertAllTrue( ( unsigned char* )bufA, sizeof( bufA ) )
             << std::endl;

   return EXIT_SUCCESS;
}

结果(许多类似结果之一):

  

strncpy:52840919,AllTrue:真

     

memcpy:57630499,AllTrue:真

     

memmove:57536472,AllTrue:true

     

mycopy:57577863,AllTrue:true

它看起来像:

  1. memcpy,memmove和我自己的方法有相似的结果;
  2. strncpy是做什么魔术的,所以它是比memcpy更快的最好的魔术?

好笑吗?