3

比memcpy还要快的内存拷贝,了解一下? - T-BARBARIANS

 1 year ago
source link: https://www.cnblogs.com/t-bar/p/17262147.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

比memcpy还要快的内存拷贝,了解一下?

本文来自博客园,作者:T-BARBARIANS,转载请注明原文链接:https://www.cnblogs.com/t-bar/p/17262147.html 谢谢!

前言

朋友们有想过居然还有比memcpy更快的内存拷贝吗?

  讲道理,在这之前我没想到过,我也一直觉得memcpy就是最快的内存拷贝方法了。

1485868-20230327165945209-437869680.png

  也不知道老板最近是咋了,天天开会都强调:“我们最近的目标就一个字:性能优化!”

  一顿操作猛如虎,也没提高5%。感觉自己实在是黔驴技穷,江郎才尽,想到又要被老板骂立马滚蛋,心里就很不是滋味。

  所谓车到山前必有路,船到桥头自然直。嘿,有一天我刚好注意到我们的业务代码里有大量的memcpy,正一筹莫展之时,突然灵光一现,脑海里闪过一个想法:memcpy还可以优化吗?

  我想说,正是这个想法又让我可以在老板面前暂时苟且偷生一段时间,实在是不得不佩服自己!

一、SIMD技术简介

  这一小节介绍的内容跟小节标题很契合,就是介绍一下SIMD(Single Instruction Multiple Data,单指令多数据),啥意思呢,就是一条指令并发处理多条数据。形象一点讲就是老板在桌上放了很多钱让你拿,有同学喜欢一张一张的拿,还说我喜欢这种慢慢富有的感觉;SIMD就是,老子一把拿,我踏马喜欢暴富!没错,它就是可以提升memcpy性能的关键核心技术。引用大佬画的一张图:

1485868-20230328102122614-1038737748.png

  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的批次拷贝。

1485868-20230328175733929-295374122.png

  (2)AVX指令集实现的fast拷贝

  与SSE指令集实现内存拷贝逻辑一致。

  1、由AVX指令集的_mm256_loadu_si256,实现每次256byte的数据加载;

  2、由AVX指令集的_mm256_storeu_si256,实现每次256byte数据的存储。

  可以预料,当然是寄存器位宽越大,性能会越好,也就是从理论上说使用AVX指令集会比SSE指令集更快。

1485868-20230328180232355-1371568354.png

 三、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更慢了,发生了什么?

1485868-20230403145831878-974965535.png

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

1485868-20230403143957182-836412528.png

1485868-20230403144335858-1728578780.png

1485868-20230403143812119-1522801819.png

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

1485868-20230403145410344-1492962506.png
  (2)AVX指令集下性能结果对比 

  同样,将AVX大长度数据拷贝也进行优化,将指令_mm256_stream_si256替换为_mm256_storeu_si256,AVX指令集的性能测试结果如下图7所示。

  简单总结为两点:

  1、图6和图7进行了充分说明,相同长度的数据拷贝,AVX确实比SSE性能更高;

  2、拷贝长度在(512 ~ 8388608)bytes,memcpy_fast都比memcpy要提升一倍不止,有的长度,内存拷贝性能甚至提升了4倍!

1485868-20230403150234800-1602662872.png

图7

四、结语

   这种内存拷贝的性能提升,有什么好处呢?

  想到一个场景,比如生产环境的网关设备(FW,VPN等等),内存拷贝的性能提升可以降低网关设备的流量处理时延,提升网络质量,从而进一步提高用户使用体验。

  把这份优化思路给老板做了汇报,老板扬起嘴角笑了笑并说道:“对你来说,饼可能不香了!”

  技术是不断实践积累的,在此分享出来与大家一起共勉!

  如果文章对你有些许帮助,还请各位技术爱好者登录点赞呀,非常感谢!

  本文来自博客园,作者:T-BARBARIANS,转载请注明原文链接:https://www.cnblogs.com/t-bar/p/17262147.html 谢谢!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK