Doris开发手记2:用SIMD指令优化存储层的热点代码
最近一直在进行Doris的向量化计算引擎的开发工作,在进行CPU热点排查时,发现了存储层上出现的CPU热点问题。于是尝试通过SIMD的指令优化了这部分的CPU热点代码,取得了较好的性能优化效果。借用本篇手记记录下问题的发现,解决过程一些对于C/C++程序性能问题的一些解决思路,希望各位也能有所收获。
1.热点代码的发现
最近在进行Doris的部分查询调优工作,通过perf定位CPU执行热点时,发现了以下的热点部分:
这里通过perf可以看到,将近一半的CPU耗时损耗在BinaryDictPageDecoder::next_batch
与BinaryPlainPageDecoder::next_batch
这两个函数上。这两部分都是字符串列进行数据读取的解码部分,所以我们得研读一下这部分代码,来看看是否有可能得优化空间。
通过Perf进一步进入函数之中,看看哪部分占用了大量的CPU。由上图可以看到大量的CPU耗时都在解码时的内存分配之上了。尤其是int64_t RoundUpToPowerOf2
这个函数的计算,这个函数是为了计算内存分配时按照对齐的内存分配的逻辑。
哪儿来的内存分配
这里得先了解Doris在Page级别是如何存储字符串类型的。这里有两种Page:
DictPage
字典编码,适合在字符串重复度较高的数据存储。Doris会将字典写入PlainPage之中,并记录每一个字符串的偏移量。而实际数据Page之中存储的不是原始的字符串了,而是偏移量了。而实际解码的时候,则需要分配内存,并从字典之中将对应偏移量的内存拷贝出来。这就是上面代码热点产生的地方。PlainPage
直接编码,适合在字符串重复度不高时。Doris会自动将DictPage转为PlainPage。而实际解码的时候,则需要分配内存,并将PlainPage的内容拷贝出来。这也是上面代码热点产生的地方。
无论是DictPage与PlainPage,解码流程都是这样。Doris每次读取的数据量是1024行,所以每次的操作都是
- 取出一行数据
- 通过数据长度,计算分配对齐内存长度
- 分配对应的内存
- 拷贝数据到分配的内存中
2.使用SIMD指令解决问题
好的,确认了问题,就开始研究解决方案。从直觉上说,将1024次零散的内存分配简化为一次大内存分配,肯定有较好的性能提升。
但是这样会导致一个很致命的问题:批量的内存分配无法保证内存的对齐,这会导致后续的访存的指令性能低下。但是为了保证内存的对齐,上面提到的尤其是int64_t RoundUpToPowerOf2
这个函数的计算是无法绕过的问题。
那既然无法绕过,我们就得想办法优化它了。这个计算是一个很简单的函数计算,所以笔者尝试是否能用SIMD指令优化这个计算流程。
2.1 什么是SIMD指令
SIMD是(Single instruction multiple data)的缩写,代表了通过单一一条指令就可以操作一批数据。通过这种方式,在相同的时钟周期内,CPU能够处理的数据的能力就大大增加了。
上图是一个简单的乘法计算,我们可以看到:4个数字都需要进行乘3的计算。这需要执行
- 4个load内存指令
- 4个乘法指令
- 4个内存回写指令
而通过SIMD指令则可以按批的方式来更快的处理数据,由上图可以看到。原先的12个指令,减少到了3个指令。当代的X86处理器通常都支持了MMX,SSE,AVX等SIMD指令,通过这样的方式来加快了CPU的计算。
当然SIMD指令也是有一定代价的,从上面的图中也能看出端倪。
- 处理的数据需要连续,并且对齐的内存能获得更好的性能
- 寄存器的占用比传统的SISD的CPU多
更多关于SIMD指令相关的信息可以参照笔者在文末留下的参考资料。
2.2 如何生成SIMD指令
通常生成SIMD指令的方式通常有两种:
Auto Vectorized
自动向量化,也就是编译器自动去分析for循环是否能够向量化。如果可以的话,便自动生成向量化的代码,通常我们开始的-O3
优化便会开启自动向量化。
这种方式当然是最简单的,但是编译器毕竟没有程序员那样智能,所以对于自动向量化的优化是相对苛刻的,所以需要程序员写出足够亲和度的代码。
下面是自动向量化的一些tips:
- 1.简单的for循环
- 2.足够简单的代码,避免:函数调用,分支跳动
- 3.规避数据依赖,就是下一个计算结果依赖上一个循环的计算结果
- 4.连续的内存与对齐的内存
手写SIMD指令
当然,本身SIMD也通过库的方式进行了支持。我们也可以直接通过Intel提供的库来直接进行向量化编程,比如SSE
的API的头文件为xmmintrin.h
, AVX
的API头文件为immintrin.h
。这种实现方式最为高效,但是需要程序员熟悉SIMD的编码方式,并且并不通用。比如实现的AVX的向量化算法并不能在不支持AVX指令集的机器上运行,也无法用SSE指令集代替。
3.开发起来,解决问题
通过上一小节对SIMD指令的分析。接下来就是如何在Doris的代码上进行开发,并验证效果。
3.1 代码开发
思路是最难的,写代码永远是最简单的。直接上笔者修改Doris的代码吧:
// use SIMD instruction to speed up call function `RoundUpToPowerOfTwo`
auto mem_size = 0;
for (int i = 0; i < len; ++i) {
mem_len[i] = BitUtil::RoundUpToPowerOf2Int32(mem_len[i], MemPool::DEFAULT_ALIGNMENT);
mem_size += mem_len[i];
}
这里利用了GCC的auto vectorized的能力,让上面的for循环能够进行向量化的计算。由于当前Doris默认的编译选项并不支持AVX指令集, 而原有的BitUtil::RoundUpToPowerOf2
的函数入参为Int64,这让只有128位的SSE指令有些捉襟见肘,所以这里笔者实现了BitUtil::RoundUpToPowerOf2Int32
的版本来加快这个过程.
// speed up function compute for SIMD
static inline size_t RoundUpToPowerOf2Int32(size_t value, size_t factor) {
DCHECK((factor > 0) && ((factor & (factor - 1)) == 0));
return (value + (factor - 1)) & ~(factor - 1);
}
如果是32位的计算,SSE指令支持128位的计算。也就是能够能够一次进行4个数字的操作。
完整的代码实现请参考这里的PR。
3.2 性能验证
Coding完成之后,编译部署,进行测试。同样用Perf进行热点代码的观察,向量化之后,对应的代码的CPU占比显著下降,执行性能得到了提升。
no vectorized | vectorized | |
---|---|---|
DictPage | 23.42% | 14.82% |
PlainPage | 23.38% | 11.93% |
随后在单机SSB的模型上测试了一下效果,可以看到不少原先在存储层较慢的查询都得到了明显的加速效果。
接着就是老方式:提出issue,把解决问题的代码贡献给Doris的官方代码仓库。完结撒花
4.小结
Bingo! 到此为止,问题顺利解决,得到了一定的性能提升。
本文特别鸣谢社区小伙伴:
- @wangbo的Code Review
- @stdpain在内存对齐上的问题的讨论。
最后,也希望大家多多支持Apache Doris,多多给Doris贡献代码,感恩~~
5.参考资料
Vectorization教程
SIMD
Apache Doris源代码
Doris开发手记2:用SIMD指令优化存储层的热点代码的更多相关文章
- Doris开发手记4:倍速性能提升,向量化导入的性能调优实践
最近居家中,对自己之前做的一些工作进行总结.正好有Doris社区的小伙伴吐槽向量化的导入性能表现并不是很理想,就借这个机会对之前开发的向量化导入的工作进行了性能调优,取得了不错的优化效果.借用本篇手记 ...
- SSE图像算法优化系列三十二:Zhang\Guo图像细化算法的C语言以及SIMD指令优化
二值图像的细化算法也有很多种,比较有名的比如Hilditch细化.Rosenfeld细化.基于索引表的细化.还有Opencv自带的THINNING_ZHANGSUEN.THINNING_GUOHALL ...
- Doris开发手记1:解决蛋疼的MySQL 8.0连接问题
笔者作为Apache Doris的开发者,平时感觉相关Doris的文章写的很少.主要是很多时候不知道应该去记录一些怎么样的问题,感觉写的不好就会很慌张.新的一年,希望记录自己在Doris开发过程之中所 ...
- Doris开发手记3:利用CoreDump文件快速定位Doris的查询问题
Apache Doris的BE部分是由C++编写,当出现一些内存越界,非法访问的问题时会导致BE进程的Crash.这部分的问题常常较难排查,同时也很难快速定位到对应的触发SQL,给使用者带来较大的困扰 ...
- 【算法随记七】巧用SIMD指令实现急速的字节流按位反转算法。
字节按位反转算法,在有些算法加密或者一些特殊的场合有着较为重要的应用,其速度也是一个非常关键的应用,比如一个byte变量a = 3,其二进制表示为00000011,进行按位反转后的结果即为110000 ...
- if-then-else、loop控制语句在SIMD指令下的后端指令生成实现--笔记
作者:Yaong 出处:https://www.cnblogs.com/yaongtime/p/14111134.html 版权:本文版权归作者和博客园共有 转载:欢迎转载,但未经作者同意,必须保留此 ...
- 用Netty开发中间件:高并发性能优化
用Netty开发中间件:高并发性能优化 最近在写一个后台中间件的原型,主要是做消息的分发和透传.因为要用Java实现,所以网络通信框架的第一选择当然就是Netty了,使用的是Netty 4版本.Net ...
- [分享]源代码&开发手记:SAE应用“车百科” (Python + SAE + Bottle + Bootstrap) - Bottle - Python4cn(news, jobs)
[分享]源代码&开发手记:SAE应用"车百科" (Python + SAE + Bottle + Bootstrap) - Bottle - Python4cn(news, ...
- HoloLens开发手记 - HoloLens真机上手简评
千呼万唤始出来,终于今天拿到了HoloLens真机. 使用体验 使用自带的应用录制了一段使用视频,如下 设备概览 包装盒 本体 试戴 实际效果 GalaxyExplorer试玩 全息图像贴到现实场景表 ...
随机推荐
- Ansible_利用系统角色重用内容
一.红帽企业Linux系统角色 1.RHEL系统角色 名称 状态 角色描述 rhel-system-roles.kdump 全面支持 配置kdump崩溃恢复服务 rhel-system-roles.n ...
- LT4020替代方案
国产 替代LT4020的方案 南芯 展讯的方案 https://item.taobao.com/item.htm?spm=a230r.1.14.21.6f27bf96rrAtci&id=56 ...
- MyBatis 模糊查询的 4 种实现方式
引言 MyBatis 有 4 种方式可以实现模糊查询. 员工信息表 ( tb_employee ) 如下: id name sex email birthday address 001 张一凡 男 z ...
- Splunk 8.2.0 发布 (macOS, Linux, Windows)
强烈鄙视 CSDN 用户 CIAS(账号:hanzheng260561728),盗用本站资源,删除原文链接,并且用于收费下载!!! 请访问原文链接:https://sysin.org/article/ ...
- Jmeter- 笔记12 - 性能测试分析 & 性能测试流程
性能测试分析 场景设计.监视图表: 设计场景:阶梯式.波浪式 监视器: 收集用于性能分析的数据:TPS图表.聚合报告\汇总报告.察看结果树.响应时间.吞吐量 服务器资源监控:cpu.内存.磁盘io 分 ...
- 5分钟就能学会的简单结构 | MLP-Mixer: An all-MLP Architecture for Vision | CVPR2021
文章转自:微信公众号「机器学习炼丹术」 作者:炼丹兄(欢迎交流,共同进步) 联系方式:微信cyx645016617 论文名称:「MLP-Mixer: An all-MLP Architecture f ...
- C# 尝试还原程序包是出错:找不到“XXXXX”版本的程序包“XXXXXX”
在C#管理程序包的时候有时会出现找不到某某版本的程序包 如果出现这样的情况,解决办法是在你当前项目获取当前类库下的packages.config里去删除一段配置就可以解决! 我的缺少的是版本为9.0. ...
- js的节流、防抖以及使用场景
介绍 首先看一个没有经过任何处理的: 1 // 模拟一个输出的函数 2 function input(value) { 3 console.log(`输入的内容${value}`) 4 } 5 con ...
- CodeGen编写自定义表达式标记
CodeGen编写自定义表达式标记 CodeGen支持开发人员通过编写plug-in modules插件模块来定义自定义表达式标记的能力,以提供与这些标记相关联的逻辑.这种plug-in module ...
- ADAS车辆在行人安全方面得分很低
ADAS车辆在行人安全方面得分很低 ADAS vehicles score poorly on pedestrian safety 对于热衷于自动驾驶汽车(AV)的狂热者来说,一个现在病毒性的视频片段 ...