专栏首页HappenLee的技术杂谈Doris开发手记2:用SIMD指令优化存储层的热点代码
原创

Doris开发手记2:用SIMD指令优化存储层的热点代码

最近一直在进行Doris的向量化计算引擎的开发工作,在进行CPU热点排查时,发现了存储层上出现的CPU热点问题。于是尝试通过SIMD的指令优化了这部分的CPU热点代码,取得了较好的性能优化效果。借用本篇手记记录下问题的发现,解决过程一些对于C/C++程序性能问题的一些解决思路,希望各位也能有所收获。

1.热点代码的发现

最近在进行Doris的部分查询调优工作,通过perf定位CPU执行热点时,发现了以下的热点部分:

perf的结果

这里通过perf可以看到,将近一半的CPU耗时损耗在BinaryDictPageDecoder::next_batchBinaryPlainPageDecoder::next_batch这两个函数上。这两部分都是字符串列进行数据读取的解码部分,所以我们得研读一下这部分代码,来看看是否有可能得优化空间。

perf的热点分析

通过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能够处理的数据的能力就大大增加了。

传统CPU的计算方式

上图是一个简单的乘法计算,我们可以看到:4个数字都需要进行乘3的计算。这需要执行

  • 4个load内存指令
  • 4个乘法指令
  • 4个内存回写指令

SIMD的计算方式

而通过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.hAVX的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的模型上测试了一下效果,可以看到不少原先在存储层较慢的查询都得到了明显的加速效果。

SSB的测试效果

接着就是老方式:提出issue,把解决问题的代码贡献给Doris的官方代码仓库。完结撒花

4.小结

Bingo! 到此为止,问题顺利解决,得到了一定的性能提升。

本文特别鸣谢社区小伙伴:

  • @wangbo的Code Review
  • @stdpain在内存对齐上的问题的讨论。

最后,也希望大家多多支持Apache Doris,多多给Doris贡献代码,感恩~~

5.参考资料

Vectorization教程 SIMD Apache Doris源代码

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Doris开发手记2:用SIMD指令优化存储层的热点代码

    最近在进行Doris的部分查询调优工作,通过perf定位CPU执行热点时,发现了以下的热点部分:

    HappenLee
  • 为什么列式存储广泛应用于OLAP领域?

    233酱工作中开始接触Presto等大数据分析场景下的内容,列式存储属于OLAP中重要的一环。这周主要花时间搜索阅读网上的相关资料,发现一众大数据、数据库开发等...

    Monica2333
  • Apache Kylin VS Apache Doris全方位对比

    本文作者:康凯森,来源于:https://blog.bcmeng.com,文章写的非常详细,从各个方面对Kylin和Doris进行了对比。

    王知无-import_bigdata
  • Apache Kylin VS Apache Doris

    本文作者:康凯森,来源于:https://blog.bcmeng.com,文章写的非常详细,从各个方面对Kylin和Doris进行了对比。

    用户6259908
  • 基于 Apache Doris 的小米增长分析平台实践

    随着小米互联网业务的发展,各个产品线利用用户行为数据对业务进行增长分析的需求越来越迫切。显然,让每个业务产品线都自己搭建一套增长分析系统,不仅成本高昂,也会导致...

    用户6259908
  • Apache Doris : 一个开源 MPP 数据库的架构与实践

    Doris 是分布式、面向交互式查询的分布式数据库,主要部分是 SQL,内部用到 MPP 技术。

    Spark学习技巧
  • 建议收藏!浅谈OLAP系统核心技术点

    OLAP系统广泛应用于BI、Reporting、Ad-hoc、ETL数仓分析等场景,本文主要从体系化的角度来分析OLAP系统的核心技术点,从业界已有的OLAP中...

    数据社
  • 这就是TDSQL的向量化执行引擎?有效降低函数调用开销,提升CPU利用率

    在“国产数据库硬核技术沙龙-TDSQL-A技术揭秘”系列分享中,5位腾讯云技术大咖分别从整体技术架构、列式存储及相关执行优化、集群数据交互总线、Fragmen...

    腾讯云数据库 TencentDB
  • 腾讯、字节争先部署,ClickHouse+Doris赶超MySQL810倍

    作为大数据从业者,你一定明白有数据是一回事,可要想让数据发挥价值、成为生产力是另一回事。手里得有两把刷子,才能成为大数据圈儿的“大拿”!

    Nauu
  • 架构师成长之路系列(二)

    行存,可以看做 NSM (N-ary Storage Model) 组织形式,一直伴随着关系型数据库,对于 OLTP 场景友好,例如 innodb[1] 的 B...

    快乐的技术人
  • 一文了解 ClickHouse 的向量化执行

    ClickHouse在计算层做了非常细致的工作,竭尽所能榨干硬件能力,提升查询速度。它实现了单机多核并行、分布式计算、向量化执行与SIMD指令、代码生成等多种重...

    Spark学习技巧
  • 【实践案例分享】Apache Doris在美团外卖数仓中的应用实践

    美团外卖数据仓库通过MOLAP+ROLAP双引擎模式来适配不同应用场景。MOLAP引擎使用了Apache Kylin。ROLAP我们经过综合考虑,选择了Apac...

    木东居士
  • 将矩阵乘法的性能提升200倍!AutoKernel算子优化工具正式开源

    随着AI技术的快速发展,深度学习在各个领域得到了广泛应用。深度学习模型能否成功在终端落地应用,满足产品需求,一个关键的指标就是神经网络模型的推理性能。于是,一大...

    AI科技评论
  • ARM Neon Intrinsics 学习指北:从入门、进阶到学个通透

    【GiantPandaCV导语】Neon是手机普遍支持的计算加速指令集,是AI落地的工程利器。Neon Intrinsics 的出现,缓解了汇编语言难学难写的...

    BBuf
  • go语言最全优化技巧总结,值得收藏!

    作者:korzhao 腾讯音乐后台开发工程师  导语 | 本文总结了在维护go基础库过程中,用到或者见到的一些性能优化技巧,现将一些理解梳理撰写成文,和大家探...

    腾讯大讲堂
  • 腾讯Kona JDK数据科学实践

    导语:开源操作系统年度技术会议(Open Source Operating System Annual Technical Conference,简称 OS2...

    腾讯大数据
  • go语言最全优化技巧总结,值得收藏!

    ? 导语 | 本文总结了在维护go基础库过程中,用到或者见到的一些性能优化技巧,现将一些理解梳理撰写成文,和大家探讨。 一、常规手段 (一)sync.Pool...

    腾小云
  • 在多维数据分析模型的路上越走越远

    数据分析和可视化一直是大数据时代的热门话题。如今这一个数据为王的时代,当你使用某个产品,划划手指,动动鼠标,甚至一颦一笑都会被记录下来,送至服务器。然而,大量的...

    tyrchen
  • 深入GPU硬件架构及运行机制

    对于大多数图形渲染开发者,GPU是既熟悉又陌生的部件,熟悉的是每天都需要跟它打交道,陌生的是GPU就如一个黑盒,不知道其内部硬件架构,更无从谈及其运行机制。

    数字芯片社区

扫码关注云+社区

领取腾讯云代金券