最近一直在进行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指令原文转载:http://www.shaoqun.com/a/859310.html
跨境电商:https://www.ikjzd.com/
hts:https://www.ikjzd.com/w/525
net a porter:https://www.ikjzd.com/w/2132
母婴团购网:https://www.ikjzd.com/w/716
最近一直在进行Doris的向量化计算引擎的开发工作,在进行CPU热点排查时,发现了存储层上出现的CPU热点问题。于是尝试通过SIMD的指令优化了这部分的CPU热点代码,取得了较好的性能优化效果。借用本篇手记记录下问题的发现,解决过程一些对于C/C++程序性能问题的一些解决思路,希望各位也能有所收获。1.热点代码的发现最近在进行Doris的部分查询调优工作,通过perf定位CPU执行热点时,发现了以
net-a-porter:https://www.ikjzd.com/w/2132
亚马逊卖家清货曙光,上线却翻车?:https://www.ikjzd.com/articles/126769
分享7个亚马逊选品小妙招!:https://www.ikjzd.com/articles/126793
确定了!亚马逊印度站Prime Day将于8月初率先举办,美国待定!:https://www.ikjzd.com/articles/126771
亚马逊仓库开始收取长期仓储费:https://www.ikjzd.com/articles/126763
学长每天都在教室要我 二个学长把我弄得好爽:http://lady.shaoqun.com/a/248128.html
同时和两个黑人老外做 一个添上面二个㖭下面:http://lady.shaoqun.com/m/a/247912.html
少妇口述:第一次尝试换妻游戏的美妙感受_第一次玩交换真实经历:http://www.30bags.com/m/a/249666.html
女海王阵型!不告诉男朋友就勾搭富二代!数百万粉丝,Tik Tok女人,网络名人,做了很多轮:http://lady.shaoqun.com/a/410933.html
清迈的一个天桥小舒林成同志,被居民举报:http://lady.shaoqun.com/a/410934.html
女人为什么选择"暧昧"?倾听中年妇女的心声:http://lady.shaoqun.com/a/410935.html
独立站高昂流量成废品,如何操作才能起死回生?(上):https://www.ikjzd.com/articles/146487
没有评论:
发表评论