Doris开发手记4:倍速性能提升,向量化导入的性能调优实践

最近居家中,对自己之前做的一些工作进行总结 。正好有Doris社区的小伙伴吐槽向量化的导入性能表现并不是很理想,就借这个机会对之前开发的向量化导入的工作进行了性能调优,取得了不错的优化效果 。借用本篇手记记录下一些性能优化的思路,抛砖引玉,希望大家多多参与到性能优化的工作总来 。
1.看起来很慢的向量化导入问题的发现来自社区用户的吐槽:向量化导入太慢了啊,我测试了xx数据库,比Doris快不少啊 。有招吗?
啊哈?慢这么多吗? 那我肯定得瞅一瞅了 。于是对用户case进行了复现,发现用户测试的是代码库里ClickBench的stream load,80个G左右的数据,向量化导入耗时得接近1200s,而非向量化导入耗时为1400s 。
向量化非向量化1230s1450sClickBench是典型的大宽表的场景,并且为Duplicate Key的模型,原则上能充分发挥向量化导入的优势 。所以看起来一定是有些问题的 , 需要按图索骥的来定位热点:
定位热点的技巧笔者通常定位Doris代码的热点有这么几种方式 , 通过这些方式共同组合 , 能帮助我们快速定位到代码真正的瓶颈点:
  • Profile: Doris自身记录的耗时,利用Profile就能分析出大致代码部分的瓶颈点 。缺点是不够灵活,很多时候需要手动编写代码,重新编译才能添加我们需要进行热点观察的代码 。
  • FlameGraph: 一旦通过Profile分析到大概的热点位置,笔者通常会快速通读一遍代码,然后结合火焰图来定位到函数热点的位置,这样进行的优化通常就有的放矢了 。关于火焰图的使用可以简要参考Doris的官方文档的开发者手册 。
  • Perf: 火焰图只能大致定位到聚合函数的热点 , 而且编译器经过内联,汇编优化之后,单纯通过火焰图的函数级别就不一定够用了 。通常需要进一步分析汇编代码的问题,这时则可以用开发手记2中提到的perf来定位汇编语言的热点 。当然,perf并不是万能的,很多时候需要我们基于代码本身的熟稔和一些优化经验来进一步进行调优 。
接下来我们就基于上述的调优思路,来一起分析一下这个问题 。
2.优化与代码解析基于火焰图,笔者梳理出在向量化导入时的几部分核心的热点 。针对性的进行了问题分析与解决:
缓慢的Cast与字符串处理在CSV导入到Doris的过程之中,需要经历一个文本数据解析,表达式CAST计算的过程 。显然,这个工作从火焰图中观察出来 , 是CPU的耗损大户
Doris开发手记4:倍速性能提升,向量化导入的性能调优实践

文章插图
上面的火焰图可以观察出来,这里有个很反常的函数调用耗时FunctionCast::prepare_remove_prepare,这里需要根据源码来进一步分析 。
在进行cast过程之中需要完成null值拆分的工作,比如这里需要完成String Cast Int的操作流程如下图所示:
Doris开发手记4:倍速性能提升,向量化导入的性能调优实践

文章插图
这里会利用原始的block,和待cast的列建立一个新的临时block来进行cast函数的计算 。
Doris开发手记4:倍速性能提升,向量化导入的性能调优实践

文章插图
上面标红的代码会对std::set进行大量的CPU计算工作,影响的向量化导入的性能 。在导入表本身是大宽表的场景下,这个问题的严重性会进一步放大 。
进行了问题定位之后 , 优化工作就显得很简单了 。显然进行cast的时候,我们仅仅只需要进行cast计算的相关列,而并不需要整个block中所有的列都参与进来 。所以笔者这里实现了一个新的函数 create_block_with_nested_columns_only_args来替换create_block_with_nested_columns_impl , 原本对100列以上的计数问题,减少为对一个列进行处理,问题得到了显著的改善 。
优化前优化后1230s980s缺页中断的优化解决了上面问题之后,继续来对火焰图进行分析,发现了在数据写入memtable时 , 产生了下面的热点:缺页中断 。
Doris开发手记4:倍速性能提升,向量化导入的性能调优实践

文章插图
这里得先简单了解一下什么是缺页中断
Doris开发手记4:倍速性能提升,向量化导入的性能调优实践

文章插图
如上图所示:CPU对数据进行计算时 , 会请求获取内存中的数据 。而CPU层级看的内存地址是:

推荐阅读