`
aigo
  • 浏览: 2537962 次
  • 性别: Icon_minigender_1
  • 来自: 宜昌
社区版块
存档分类
最新评论

比memcpy更快的内存拷贝:用赋值代替循环拷贝

阅读更多

原文是出自百度空间,百度空间早已关闭,所以原文出处无法查询了

 

相关衍生:

怎样写出一个更快的 memset/memcpy ?

https://www.zhihu.com/question/35172305 

 

以下内容转自:http://www.cnblogs.com/GoodGoodWorkDayDayUp/archive/2010/10/15/1852251.html

然间看到一个叫xmemcpy的工具,用做内存拷贝。号称在拷贝120字节以内时,比glibc提供的memcpy快10倍,并且有实验数据。

 

这让人感觉很诧异。一直以来都觉得memcpy是很高效的。相比于strcpy等函数的逐字节拷贝,memcpy是按照机器字长逐字进行拷贝的,一个字等于4(32位机)或8(64位机)个字节。CPU存取一个字节和存取一个字一样,都是在一条指令、一个内存周期内完成的。显然,按字拷贝效率更高。

 

那么,这个xmemcpy是靠什么来实现比memcpy“快10倍”的呢?

看了一下xmemcpy的实现,原来它速度快的根据是:“小内存的拷贝,使用等号直接赋值比memcpy快得多”。

这下就更纳闷了,内存拷贝不就是把一块内存一部分一部分地拷贝到另一块内存去吗?难道逐字拷贝还有性能提升的空间?

 

写了一段代码:

#include <stdio.h>

#define TESTSIZE        128

struct node {
	char buf[TESTSIZE];
};
void main()
{
	char src[TESTSIZE] = {0};
	char dst[TESTSIZE];
	*(struct node*)dst = *(struct node*)src;
}

 

 

然后反汇编:

......
00000000004004a8 <main>:
4004a8:       55                      push   %rbp
4004a9:       48 89 e5                mov    %rsp,%rbp
4004ac:       48 81 ec 00 01 00 00    sub    $0x100,%rsp
4004b3:       48 8d 7d 80             lea    0xffffffffffffff80(%rbp),%rdi
4004b7:       ba 80 00 00 00          mov    $0x80,%edx
4004bc:       be 00 00 00 00          mov    $0x0,%esi
4004c1:       e8 1a ff ff ff          callq 4003e0 <memset@plt>
4004c6:       48 8b 45 80             mov    0xffffffffffffff80(%rbp),%rax
4004ca:       48 89 85 00 ff ff ff    mov    %rax,0xffffffffffffff00(%rbp)
4004d1:       48 8b 45 88             mov    0xffffffffffffff88(%rbp),%rax
......
400564:       48 89 85 70 ff ff ff    mov    %rax,0xffffffffffffff70(%rbp)
40056b:       48 8b 45 f8             mov    0xfffffffffffffff8(%rbp),%rax
40056f:       48 89 85 78 ff ff ff    mov    %rax,0xffffffffffffff78(%rbp)
400576:       c9                      leaveq 
400577:       c3                      retq   
400578:       90                      nop    
......

 

 

再将libc反汇编,并找到memcpy的实现,以作比较:

......
0006b400 <memcpy>:
6b400:       8b 4c 24 0c             mov    0xc(%esp),%ecx
6b404:       89 f8                   mov    %edi,%eax
6b406:       8b 7c 24 04             mov    0x4(%esp),%edi
6b40a:       89 f2                   mov    %esi,%edx
6b40c:       8b 74 24 08             mov    0x8(%esp),%esi
6b410:       fc                      cld    
6b411:       d1 e9                   shr    %ecx
6b413:       73 01                   jae    6b416 <memcpy+0x16>
6b415:       a4                      movsb %ds:(%esi),%es:(%edi)
6b416:       d1 e9                   shr    %ecx
6b418:       73 02                   jae    6b41c <memcpy+0x1c>
6b41a:       66 a5                   movsw %ds:(%esi),%es:(%edi)
6b41c:       f3 a5                   repz movsl %ds:(%esi),%es:(%edi)
6b41e:       89 c7                   mov    %eax,%edi
6b420:       89 d6                   mov    %edx,%esi
6b422:       8b 44 24 04             mov    0x4(%esp),%eax
6b426:       c3                      ret    
6b427:       90                      nop    
......

 

 

原来两者都是通过逐字拷贝来实现的。但是“等号赋值”被编译器翻译成一连串的MOV指令,而memcpy则是一个循环。“等号赋值”比memcpy快,并不是快在拷贝方式上,而是快在程序流程上。

(另外,测试发现,“等号赋值”的长度必须小于等于128,并且是机器字长的倍数,才会被编译成连续MOV形式,否则会被编译成调用memcpy。当然,具体怎么做是编译器决定的。)

 

而为什么同样是按机器字长拷贝,连续的MOV指令就要比循环MOV快呢?

在循环方式下,每一次MOV过后,需要:1、判断是否拷贝完成;2、跳转以便继续拷贝。

每拷贝一个字长,CPU就需要多执行以上两个动作。

 

循环除了增加了判断和跳转指令以外,对于CPU处理流水产生的影响也是不可不计的。CPU将指令的执行分为若干个阶段,组成一条指令处理流水线,这样就能实现在一个CPU时钟周期完成一条指令,使得CPU的运算速度得以提升。

指令流水只能按照单一的指令路径来执行,如果出现分支(判断+跳转),流水就没法处理了。

为了缓解分支对于流水的影响,CPU可能会采取一定的分支预测策略。但是分支预测不一定就能成功,如果失败,其损失比不预测还大。

 

所以,循环还是比较浪费的。如果效率要求很高,很多情况下,我们需要把循环展开(比如在本例中,每次循环拷贝N个字节),以避免判断与跳转占用大量的CPU时间。这算是一种以空间换时间的做法。GCC就有自动将循环展开的编译选项(如:-funroll-loops)。

但是,循环展开也是应该有个度的,并不是越展开越好(即使不考虑对空间的浪费)。因为CPU的快速执行很依赖于cache,如果cache不命中,CPU将浪费不少的时钟周期在等待内存上(内存的速度一般比CPU低一个数量级)。而小段循环结构就比较有利于cache命中,因为重复执行的一段代码很容易被硬件放在cache中,这就是代码局部性带来的好处。而过度的循环展开就打破了代码的局部性,所以xmemcpy一开始就提到拷贝120字节以内。如果要拷贝的字节更多,则全部展开成连续的MOV指令的做法未必会很高效。

 

综上所述,“等号赋值”之所以比memcpy快,就是因为它省略了CPU对于判断与跳转的处理,消除了分支对CPU流水的影响。而这一切都是通过适度展开内存拷贝的循环来实现的。

 

 

分享到:
评论

相关推荐

    memcpy, memcpy()函数,字符串拷贝

    memcpy: 头文件,函数原型,函数功能,函数返回值,说明

    内存拷贝的优化方法——

    以P4平台下内存拷贝操作为例,根据AMD提供的优化文档中的例子,介绍如何通过特定指令集,优化内存带宽的使用

    C语言中memcpy 函数的用法详解

    c和c++使用的内存拷贝函数,memcpy函数的功能是从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中。 void* memcpy(void* destination, const void* source, size_t num); void*...

    字符串及其操作以及函数实现

    字符操作及其有关函数实现字符串复制函数: char * my_strcpy(char * dest, const char * src); 字符串拼接函数: char *my_... 实现 memcpy 拷贝:内存拷贝: void * memcpy(void * dest, const void * src, size_t len);

    C实现内存拷贝以及字符串拷贝函数

    C实现内存拷贝以及字符串拷贝函数。。。。。。。。。

    C++中memcpy和memmove的区别总结

    memcpy内存拷贝,没有问题;memmove,内存移动?错,如果这样理解的话,那么这篇文章你就必须要好好看看了,memmove还是内存拷贝。那么既然memcpy和memmove二者都是内存拷贝,那二者究竟有什么区别呢? 先说memcpy 你...

    C函数之memcpy()函数用法

    函数原型  void *memcpy(void*dest, const void *src, size_t n);  功能  由src指向地址为起始地址的连续n个字节的数据复制到以destin指向地址为起始...  memcpy用来做内存拷贝,你可以拿它拷贝任何数据类型

    C/C++面试之算法系列--几个典型的内存拷贝及字符串函数实现

    C/C++面试之算法系列--几个典型的内存拷贝及字符串函数实现 写一个函数,完成内存之间的拷贝。[考虑问题是否全面,是否考虑内存重叠问题] 返回void *支持链式操作,参数类型是void *以支持任意类型的指针,输入...

    memcpy_sse:使用SSE2 loadstore instrinsics的memcpy()

    memcpy_sse ... 在Linux上,无论是32位还是64位,它似乎都比memcpy()更快。 32位Linux: g++ memcpy_test.cpp -o memcpy_test -O2 -msse2 64位Linux: g++ memcpy_test.cpp -o memcpy_test -O2

    C++执行内存memcpy的效率测试

    C++执行内存memcpy的效率测试

    memcpy的用法

    memcpy 函数用于 把资源内存(src所指向的内存区域) 拷贝到目标内存(dest所指向的内存区域)

    memcpy函数的实现

    一个使用memcpy的实现版本,考虑内存重叠以及效率

    字符串拷贝函数memcpy和strncpy以及snprintf 的性能比较

    问题:函数memcpy(dest, src, sizeof(dest))、strncpy(dest, src, sizeof(dest))和snprintf(dest, sizeof(dest), “%s”, src)都可以将src字符串中的内容拷贝到dest字符串中。哪一种方式效率最高呢?就是说,哪种...

    一个循环线性地址先进先出队列(FIFO),

    一个循环线性地址先进先出...2.线性地址空间,直接使用memcpy进出,速度非常快; 使用说明: 1.fifo_init 2.fifo_into 2.fifo_get 3.fifo_remove 4.fifo_uninit 如发现问题联系本人: lxj_com@163.com QQ:67016879

    linux memcpy源码

    linux内核源码:memcpy,对比这个就发现了自己的不足,memcpy真能考验程序员的编程技能与逻辑思维

    memcpy的实现

    内存拷贝的实现函数。此函数完全可以自己用,如果需要可以自己改进。

    基于C++执行内存memcpy效率测试的分析

    如下基于8K的内存快执行memcpy, 1个线程大约1S能够拷贝500M,如果服务器带宽或网卡到上限是1G,那么网络io的work thread 开2个即可,考虑到消息的解析损耗,3个线程足以抗住硬件的最高负载。 在我到测试机器上到测试...

    memcpy 优化---性能大幅提升几倍

    。虽然因为硬件限制没有达到AMD文档中所说memcpy函数300%的性能提升,但在我机器上实测也有%175-%200的明显性能提升(此数据可能根据机器情况不同)。

    memcpy 和strcpy 的区别

    memcpy 和strcpy 的区别

Global site tag (gtag.js) - Google Analytics