比memcpy还要快的内存拷贝,老哥了解一下?
本文来自博客园,作者:T-BARBARIANS,转载请注明原文链接:https://www.cnblogs.com/t-bar/p/17262147.html 谢谢!
前言
朋友们有想过居然还有比memcpy更快的内存拷贝吗?
讲道理,在这之前我没想到过,我也一直觉得memcpy就是最快的内存拷贝方法了。

也不知道老板最近是咋了,天天开会都强调:“我们最近的目标就一个字:性能优化!”
一顿操作猛如虎,也没提高5%。感觉自己实在是黔驴技穷,江郎才尽,想到又要被老板骂立马滚蛋,心里就很不是滋味。
所谓车到山前必有路,船到桥头自然直。嘿,有一天我刚好注意到我们的业务代码里有大量的memcpy,正一筹莫展之时,突然灵光一现,脑海里闪过一个想法:memcpy还可以优化吗?
我想说,正是这个想法又让我可以在老板面前暂时苟且偷生一段时间,实在是不得不佩服自己!
一、SIMD技术简介
这一小节介绍的内容跟小节标题很契合,就是介绍一下SIMD(Single Instruction Multiple Data,单指令多数据),啥意思呢,就是一条指令并发处理多条数据。形象一点讲就是老板在桌上放了很多钱让你拿,有同学喜欢一张一张的拿,还说我喜欢这种慢慢富有的感觉;SIMD就是,老子一把拿,我踏马喜欢暴富!没错,它就是可以提升memcpy性能的关键核心技术。引用大佬画的一张图:

图1
Scalar Operation就是指的SISD(Single Instruction Single Data,单指令单数据),这种方式完成上图所有C[i]的计算需要串行执行八次,因为每个时间点,CPU的一条指令只能执行一份数据。
SIMD,就是一次运算就可以得到上述SISD的多次运算结果,即一条指令可以并发执行多份数据,因此SIMD也称为向量化计算。
到底是什么奇技淫巧使得SIMD具有并发执行多份数据的能力呢?
其实就是CPU增加了专门用于向量化计算的向量寄存器,这些寄存器跟普通的寄存器不太一样,它们的位宽都比较大,比如有128bit,256bit,甚至512bit,也就是说这些寄存器可以分别一次存储16byte,32byte,64byte的数据。比如上图的加法运算,SISD一条指令只能完成一次两个8byte数据的加法运算。但是SIMD,一条指令就可以完成a[0:7] + b[0:7] = c[0:7],两组数据的加法运算。
CPU除了增加向量寄存器,还为向量寄存器配套了专门的指令集,比如Intel的MMX,SSE(MMX的升级版),AVX(SSE的升级版)指令集。CPU运算时,识别到指令集命令,就会采用指令集对应的SIMD计算方法完成并发运算。Intel指令集查询链接:http://kntan.top/#!=undefined
二、memcpy_fast方法
带着memcpy是否还可以继续优化的疑问,一通搜索,真找到了采用SIMD技术的memcpy方法:memcpy_fast,链接:https://github.com/skywind3000/FastMemcpy
分析了一下源码实现
(1)SSE指令集实现的fast拷贝
1、使用_mm_loadu_si128指令,从src + 0的位置取走128bit,即16字节,然后依次类推,src + 1,...,直至src + 7,一共取走16byte * 8=128byte,取出的内容分别储存到向量寄存器c0,c1,...,c7;
2、使用_mm_prefetch实现数据预取,提前把数据从内存加载到cache,保证CPU对数据的快速读取;
3、使用_mm_store_si128指令,将c0,c1,...,c7寄存器的内容分别存储至目的地址dst + 0, dst + 1,..., dst + 7的八个位置。
利用指令集、向量寄存器、数据预取技术实现了每次16byte的并发,128byte的批次拷贝。

图2
(2)AVX指令集实现的fast拷贝
与SSE指令集实现内存拷贝逻辑一致。
1、由AVX指令集的_mm256_loadu_si256,实现每次256byte的数据加载;
2、由AVX指令集的_mm256_storeu_si256,实现每次256byte数据的存储。
可以预料,当然是寄存器位宽越大,性能会越好,也就是从理论上说使用AVX指令集会比SSE指令集更快。

图3
三、memcpy VS memcpy_fast
我们一起来看看memcpy与使用了SIMD技术的memcpy_fast的性能对比吧。
直接将memcpy_fast源码下载后编译即可,链接:https://github.com/skywind3000/FastMemcpy
SSE指令集编译命令:gcc -O3 -msse2 FastMemcpy.c -o FastMemcpy
AVX指令集编译命令:gcc -O3 -mavx FastMemcpy_Avx.c -o FastMemcpy_Avx
(1)SSE指令集下性能结果对比
绿色框里,即内存拷贝在1MB以下时,特别是拷贝长度在(1024 ~ 1048576)bytes时,拷贝性能有显著提升。但是靠拷贝长度超过1MB时,memcpy_fast居然比memcpy更慢了,发生了什么?

图4
继续查阅源码,发现在大于2MB时,与2MB长度以下的拷贝相比,采用了不同的SIMD拷贝指令。即在拷贝长度小于等于 cachesize = 0x200000 时,使用 _mm_store_si128进行数据存储;在大于0x200000 时,使用_mm_stream_si128进行数据存储。



图5
我把大长度数据拷贝由_mm_stream_si128替换为中等长度数据拷贝指令_mm_store_si128后,memcpy_fast无论是中等长度,还是大长度的数据拷贝性能都比memcpy要好。

同样,将AVX大长度数据拷贝也进行优化,将指令_mm256_stream_si256替换为_mm256_storeu_si256,AVX指令集的性能测试结果如下图7所示。
简单总结为两点:
1、图6和图7进行了充分说明,相同长度的数据拷贝,AVX确实比SSE性能更高;
2、拷贝长度在(512 ~ 8388608)bytes,memcpy_fast都比memcpy要提升一倍不止,有的长度,内存拷贝性能甚至提升了4倍!
图7
四、结语
这种内存拷贝的性能提升,有什么好处呢?
想到一个场景,比如生产环境的网关设备(FW,VPN等等),内存拷贝的性能提升可以降低网关设备的流量处理时延,提升网络质量,从而进一步提高用户使用体验。
把这份优化思路给老板做了汇报,老板扬起嘴角笑了笑并说道:“对你来说,饼可能不香了!”
技术是不断实践积累的,在此分享出来与大家一起共勉!
如果文章对你有些许帮助,还请各位技术爱好者登录点赞呀,非常感谢!
本文来自博客园,作者:T-BARBARIANS,转载请注明原文链接:https://www.cnblogs.com/t-bar/p/17262147.html 谢谢!
比memcpy还要快的内存拷贝,老哥了解一下?的更多相关文章
- C语言中的字符串拷贝函数strcpy和内存拷贝函数memcpy的区别与实现
strcpy和memcpy都是标准C库函数,它们有下面的特点. strcpy提供了字符串的复制.即strcpy只用于字符串复制,并且它不仅复制字符串内容之外,还会复制字符串的结束符'\0'. 已知st ...
- c++中内存拷贝函数(C++ memcpy)详解
原型:void*memcpy(void*dest, const void*src,unsigned int count); 功能:由src所指内存区域复制count个字节到dest所指内存区域. 说明 ...
- C 和 C++语言中的内存拷贝函数memcpy()
memcpy指的是C和C++使用的内存拷贝函数 函数原型为void *memcpy(void *destin, void *source, unsigned n): 函数的功能是从源内存地址的起始位置 ...
- memcpy内存拷贝及优化策略图解
一般内存拷贝与优化 代码实现 #include<iostream> usingnamespace std; //不安全的内存拷贝(当源内存地址与目标内存地址重叠时会产生错误) void h ...
- emplace_back减少内存拷贝和移动
--------<深入应用C++11:代码优化与工程级应用>第2章使用C++11改进程序性能,本章将分别介绍右值引用相关的新特性.本节为大家介绍emplace_back减少内存拷贝和移动. ...
- CUDA零内存拷贝 疑问考证
今天思考了一下CUDA零内存拷贝的问题,感觉在即将设计的程序中会派上用场,于是就查了一下相关信息. 以下是一些有帮助的链接: cuda中的零拷贝用法--针对二维指针 cuda中的零拷贝用法--针对一维 ...
- C++11如何减少内存拷贝次数
C++11中出现了很多迷人的特性.例如智能指针实现高效的内存管理,std::bind和std::function函数封装器,以及lambda实现的函数对象语法糖,都是使我着迷的地方. 而C++11最大 ...
- opencv roi resize 会导致内存拷贝产生子图像
opencv roi区域 resize之后,roi的引用已不是原图的引用,而是内存拷贝产生的子图像. http://blog.csdn.net/qianqing13579/article/detail ...
- CUDA内存拷贝
原文链接1.cudaMemcpy()<--> cudaMalloc() //线性内存拷贝 1 //线性内存拷贝 2 cudaMalloc((void**)&dev_A, data ...
- 【CUDA开发】CUDA面内存拷贝用法总结
[CUDA开发]CUDA面内存拷贝用法总结 标签(空格分隔): [CUDA开发] 主要是在调试CUDA硬解码并用D3D9或者D3D11显示的时候遇到了一些代码,如下所示: CUdeviceptr g_ ...
随机推荐
- react+antd pro实现【列表可实时行内编辑】的弹窗表单组件
纯列表版效果展示: ① 初始无值,展示为唤醒按钮+文案外链 ②点击按钮唤醒弹窗(简易版示意图) ③配置后 可编辑表格组件文档: https://procomponents.ant.design/com ...
- 【已解决】xlwings库合并单元格,不报错,不执行
源代码如下: import xlwings as xw app = xw.App(visible=True, add_book=False) app.display_alerts = False ap ...
- 类内函数的override问题-方法
Question: have a base class with a virtual function: class Base { public: virtual void Function(); } ...
- Latex Algorithm 语法错误导致无法编译
遇到了几种情况: 1. for 循环没加{} 2. $\textbf{T_i}$, 想要加粗T,但是把i也扩进去了,latex就不懂了,于是一直recompile不出来说超时什么什么的,把i放到外面就 ...
- 【转载】Python 在已存在的excel表格中写入数据
由于每天都要汇总日报数据,其实就是个复制粘贴的工作,将多个表的数据汇总成一个表格,工作较枯燥,于是想到用Python帮我完成这个简单又粗糙的工作.写一下我学到的几个小技巧,下次忘记怎么使用的时候,还能 ...
- 互联网架构 之备份服务器 rsync
备份服务器(Rsync) 1.介绍备份的场景 1.什么情况下需要备份 一般数据比较重要的情况下,数据如果丢失很容易找不回来了的,建议备份. 2.什么是备份 将原有的数据复制一份或几份存起来,以备不时之 ...
- 04 Hadoop思想与原理
Hadoop最早起源于Nutch.Nutch的设计目标是构建一个大型的全网搜索引擎,包括网页抓取.索引.查询等功能,但随着抓取网页数量的增加,遇到了严重的可扩展性问题--如何解决数十亿网页的存储和索引 ...
- LeetCode 之 108. 将有序数组转换为二叉搜索树
原题链接 思路: 二叉搜索树的定义: 它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值: 若它的右子树不空,则右子树上所有结点的值均大于它的 ...
- 洛谷P1118数字三角形,
#include <bits/stdc++.h> using namespace std; int c[13][13];//杨辉三角 int b[13];//用于排除 int a[13]; ...
- Angular 管道和路由
管道pipe: 要在HTML模板中指定值转换,请使用管道运算符(|). {{interpolated_value | pipe_name}} 您可以链接管道,发送一个管道功能的输出以被另一个管道功能转 ...