草庐IT

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

HappenLee 2023-03-28 原文

最近居家中,对自己之前做的一些工作进行总结。正好有Doris社区的小伙伴吐槽向量化的导入性能表现并不是很理想,就借这个机会对之前开发的向量化导入的工作进行了性能调优,取得了不错的优化效果。借用本篇手记记录下一些性能优化的思路,抛砖引玉,希望大家多多参与到性能优化的工作总来。

1.看起来很慢的向量化导入

问题的发现

来自社区用户的吐槽:向量化导入太慢了啊,我测试了xx数据库,比Doris快不少啊。有招吗?

啊哈?慢这么多吗? 那我肯定得瞅一瞅了。
于是对用户case进行了复现,发现用户测试的是代码库里ClickBench的stream load,80个G左右的数据,向量化导入耗时得接近1200s,而非向量化导入耗时为1400s。

向量化 非向量化
1230s 1450s

ClickBench是典型的大宽表的场景,并且为Duplicate Key的模型,原则上能充分发挥向量化导入的优势。所以看起来一定是有些问题的,需要按图索骥的来定位热点:

定位热点的技巧

笔者通常定位Doris代码的热点有这么几种方式,通过这些方式共同组合,能帮助我们快速定位到代码真正的瓶颈点

  • Profile: Doris自身记录的耗时,利用Profile就能分析出大致代码部分的瓶颈点。缺点是不够灵活,很多时候需要手动编写代码,重新编译才能添加我们需要进行热点观察的代码。

  • FlameGraph: 一旦通过Profile分析到大概的热点位置,笔者通常会快速通读一遍代码,然后结合火焰图来定位到函数热点的位置,这样进行的优化通常就有的放矢了。关于火焰图的使用可以简要参考Doris的官方文档的开发者手册

  • Perf: 火焰图只能大致定位到聚合函数的热点,而且编译器经过内联,汇编优化之后,单纯通过火焰图的函数级别就不一定够用了。通常需要进一步分析汇编代码的问题,这时则可以用开发手记2中提到的perf来定位汇编语言的热点。当然,perf并不是万能的,很多时候需要我们基于代码本身的熟稔和一些优化经验来进一步进行调优。

接下来我们就基于上述的调优思路,来一起分析一下这个问题。

2.优化与代码解析

基于火焰图,笔者梳理出在向量化导入时的几部分核心的热点。针对性的进行了问题分析与解决:

缓慢的Cast与字符串处理

在CSV导入到Doris的过程之中,需要经历一个文本数据解析,表达式CAST计算的过程。显然,这个工作从火焰图中观察出来,是CPU的耗损大户

上面的火焰图可以观察出来,这里有个很反常的函数调用耗时FunctionCast::prepare_remove_prepare,这里需要根据源码来进一步分析。

在进行cast过程之中需要完成null值拆分的工作,比如这里需要完成String Cast Int的操作流程如下图所示:

这里会利用原始的block,和待cast的列建立一个新的临时block来进行cast函数的计算。


上面标红的代码会对std::set进行大量的CPU计算工作,影响的向量化导入的性能。在导入表本身是大宽表的场景下,这个问题的严重性会进一步放大。

进行了问题定位之后,优化工作就显得很简单了。显然进行cast的时候,我们仅仅只需要进行cast计算的相关列,而并不需要整个block中所有的列都参与进来。所以笔者这里实现了一个新的函数 create_block_with_nested_columns_only_args来替换create_block_with_nested_columns_impl,原本对100列以上的计数问题,减少为对一个列进行处理,问题得到了显著的改善。

优化前 优化后
1230s 980s
缺页中断的优化

解决了上面问题之后,继续来对火焰图进行分析,发现了在数据写入memtable时,产生了下面的热点:缺页中断

这里得先简单了解一下什么是缺页中断

如上图所示:CPU对数据进行计算时,会请求获取内存中的数据。而CPU层级看的内存地址是:Virtual Address需要经过特别的CPU结构MMU进行虚拟地址到物理地址的映射。而MMU会到TLB(Translation lookaside buffer,记住这个是个缓存),查找对应的虚拟地址到物理地址的映射。由于操作系统中,内存都是通过页进行管理的,地址都是基于页内存地址的偏移量,所以这个过程变成了查找起始页地址的一个工作。如果目标虚存空间中的内存页,在物理内存中没有对应的页映射,那么这种情况下,就产生了缺页中断(Page Fault)

缺页中断显然会带来一些额外的开销:

  • 用户态到内核态的切换
  • 内核处理缺页错误

所以,频繁的出现缺页中断,对导入的性能产生了不利的影响,需要尝试解决它。

内存复用

这里大量的内存使用,取址都是对于Column进行操作导致的,所以得尝试从内存分配的源头来解决这个问题。

解决思路也很简单,既然缺页中断是内存没有映射引起的,那这里就尽量复用之前已经使用过的内存,这样,自然也不会引起缺页中断的问题了,对于TLB的缓存访问也有了更高的亲和度。

Doris内部本身支持了ChunkAlloctor的类来进行内存分配,复用,绑核的逻辑,通过ChunkAlloctor能大大提升内存申请的效率,对于当前case的缺页中断也能起到规避的效果:

通过替换podarray的内存分配的逻辑之后,效果也很符合预期,通过火焰图进行观察,缺页中断的占比大量的减少,性能上也获得了可观的收益。

优化前 优化后
980s 776s

3.一些相关的优化的TODO:

  • CSV的数据格式解析:通过4kb的cache 来预取多行数据,利用并SIMD指令集来进一步性能优化

  • 缺页中断的优化:部分内存分配拷贝过程之中的page fault的问题, 可以考虑引入大页内存机制来进一步进行缺页中断,页内存cache的优化

4.小结

当然,笔者进行的向量化导入工作只是Doris向量化导入中的一部分工作。很多社区的同学也深入参与了相关工作,在当前的基础上又有得到了更为理想的性能表现。总之,性能优化的工作是永无止境的.

这里也特别鸣谢社区的两位同学的code review和分析帮助:xinyiZzz, Gabriel

Bingo!请大家期待下一个1.2版本全面向量化的Doris,相信在性能和稳定性上,一定会带给各位惊喜

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

5.参考资料

Page Fault
Apache Doris源代码

有关Doris开发手记4:倍速性能提升,向量化导入的性能调优实践的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  3. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  4. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  5. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  6. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  7. ruby - 检查是否通过 require 执行或导入了 Ruby 程序 - 2

    如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby​​文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否

  8. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  9. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  10. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

随机推荐