比memcpy更快的内存拷贝

偶然间看到一个叫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 <>
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流水的影响。而这一切都是通过适度展开内存拷贝的循环来实现的。

时间: 2024-10-31 00:33:17

比memcpy更快的内存拷贝的相关文章

代码-比库函数memcpy更高效的实现

问题描述 比库函数memcpy更高效的实现 求一个比库函数memcpy更高效的内存拷贝函数显示,求代码!采纳的有积分奖励 解决方案 编译器已经对memcpy做了很多中优化了 VC 对 memcpy 的优化http://blog.codingnow.com/2005/10/vc_memcpy.html 解决方案二: 编译器已经考虑各种优化可能,以及兼容性,各种极端测试情况等. 你想做的比编译器更好,不是那么容易 解决方案三: 当然可以看看VC,GCC, CLang对memcpy的实现,会有一些区别

PostgreSQL 10.0 preview 性能增强 - (多维分析)更快,更省内存hashed aggregation with grouping sets

标签 PostgreSQL , 10.0 , hashed aggregation with grouping sets 背景 grouping sets 是多维分析语法,PostgreSQL 从9.5开始支持这种语法,常被用于OLAP系统,数据透视等应用场景. <PostgreSQL 9.5 new feature - Support GROUPING SETS, CUBE and ROLLUP.> 由于多维分析的一个QUERY涉及多个GROUP,所以如果使用hash agg的话,需要多个H

哪个更快:Java堆还是本地内存

使用Java的一个好处就是你可以不用亲自来管理内存的分配和释放.当你用new关键字来实例化一个对象时,它所需的内存会自动的在Java堆中分配.堆会被垃圾回收器进行管理,并且它会在对象超出作用域时进行内存回收.但是在JVM中有一个'后门'可以让你访问不在堆中的本地内存(native memory).在这篇文章中,我会给你演示一个对象是怎样以连续的字节码的方式在内存中进行存储,并且告诉你是应该怎样存储这些字节,是在Java堆中还是在本地内存中.最后我会就怎样从JVM中访问内存更快给一些结论:是用Ja

移动设计优化:让APP变得更快的设计方法

文章描述:我们都知道不管网页还是移动应用,响应速度都是最重要的体验指标之一,并且移动应用的网络环境不稳定,速度的体验显得尤为重要.其实速度优化不仅是程序员的事,设计,也能够让App变得更快. D.A:我们都知道不管网页还是移动应用,响应速度都是最重要的体验指标之一,并且移动应用的网络环境不稳定,速度的体验显得尤为重要.其实速度优化不仅是程序员的事,设计,也能够让App变得更快. 1.后台执行 这是一条很通用,也容易理解的方法.用户不会愿意盯着进度条傻傻地等待,除了"取消"没有其他选择.

让电脑启动更快的十五招

嫌脑启动太慢是每个脑迷的共同心病,让脑启动更快是大家的共同心愿,本人在使用脑过程中总结了加快脑启动速度的"十五式",与您分享. 一.BIOS的优化设置 在BIOS设置的首页我们进入"Advanced BIOS Features"选项,将光标移到"Frist Boot Device"选项,按"PageUP"和"PageDOWN"进行选择,默认值为"Floppy",这表示启动时系统会先从软驱

在Java中使用启发式搜索更快地解决问题

了解一个流行人工智能搜索算法的 Java 实现 通过搜寻可行解决方案空间来解决问题是人工智能中一项名为状态空间搜索 的基本技术. 启发式搜 索 是状态空间搜索的一种形式,利用有关一个问题的知识来更高效地查找解决方案.启发式搜索在各个 领域荣获众多殊荣.在本文中,我们将向您介绍启发式搜索领域,并展示如何利用 Java 编程语言实现 A*,即最广为使用的启发式搜索算法.启发式搜索算法对计算资源和内存提出了较高的要求.我们还将展 示如何避免昂贵的垃圾收集,以及如何利用一个替代的高性能 Java 集合框

让Windows XP跑得更快更稳

微软的XP系统被大多数网民称为是历史上最优秀的操作系统,有眼花缭乱的功能.更快的速度,当然这一切都对计算机的硬件提出了更高的要求,如果你希望WindowsXP能够尽可能少地占用你有限的系统资源,不妨根据自己的需要对它进行一次"小手术". 虽然XP被微软自称为有史以来最稳定.功能最强大的Windows操作系统,并且运行速度飞快--启动速度甚至比同样配置的Windows 2000还要快许多,你可能依然不满足于此,希望让XP发挥其最佳性能,或者你的硬件配置不是太高,想让Windows XP运

巧设msconfig 让Windows7启动更快一步

  我们知道,在Windows系统里,有个非常实用的程序--msconfig,即系统配置实用程序.该程序为系统启动和加载项设置,合理的配置可以大大提升系统的启动速度和运行效率. 在Windows7系统里,我们可以通过在开始菜单的搜索框里直接输入msconfig后回车,即可打开"系统配置"窗口. ▲在开始菜单里输入程序命令 ▲弹出的系统配置 由于安装的程序越来越多,并且现在很多程序都默认启动系统自动加载,这无疑会使系统启动变得很慢,比如杀毒软件.办公软件.一些优化软件等,在启动系统时,就

电脑启动更快的15种方法

电脑启动更快的15种方法 嫌电脑启动太慢是每个电脑迷的共同心病,让电脑启动更快是大家的共同心愿,本人在使用电脑过程中总结了加快电脑 启动速度的"十五式",与您分享. 一.BIOS的优化设置 在BIOS设置的首页我们进入"Advanced BIOS Features"选项,将光标移到"Frist Boot Device"选项, 按"PageUP"和"PageDOWN"进行选择,默认值为"Floppy