深度解读数据库引入 LLVM 技术后如何提升性能
![深度解读数据库引入LLVM技术后如何提升性能](https://static001.geekbang.org/infoq/c0/c0290b8b52916387abd1a02f7a373ddb.jpeg)
GaussDB 作为企业级的数据库,经过了多年的技术发展,具备丰富的技术特性,使用 LLVM 技术后提升了系统的查询性能,使得开发者在 OLAP 和 OLTP 多场景中均受益。
Hi,别急!让技术触达每一个角落,赋能更多的人,GaussTech 第 3 期《LLVM 技术在 GaussDB 等数据库中的应用》,不仅带来满满的技术干货,还推出【分享集赞回帖赢好礼】活动,参与就能赢好礼,点击链接参与!
万物互联的态势下,数据量的激增使得“如何提升数据处理性能”成为各家数据库共同面临的挑战。作为编译优化技术的代表,基于 LLVM 的 CodeGen 技术,能为每个查询生成定制的机器码替代原本的通用函数,减少实际查询时冗余的条件逻辑判断、虚函数调用并提高数据局域性,从而达到提升查询整体性能的目的,成为数据库性能优化的一项重要技术。
LLVM 能在分析类场景中给用户带来较大的收益,也能在特定的交易性场景中给用户带来一定的收益。接下来详细解读一下 LLVM 技术在 GaussDB 中的应用吧。
LLVM 和数据库
LLVM(Low Level Virtual Machine)是一款流行的开源编译器框架,是 CodeGen(生成源代码的工具)技术的事实标准,被广泛运用于数据库(如 KES,AnalyticDB,GaussDB)、大数据(如 Spark)、AI 平台(如 tensorflow)等领域,用于提升数据处理的性能。
在没有引入 LLVM 这类 CodeGen 技术之前,数据库会使用通用的处理逻辑来处理数据,但通用逻辑“笨重”(递归、封装、类型判断转换)的代码实现方式,存在虚函数开销、缓存使用率低下、对指令集不敏感等性能短板。
引入 LLVM 之后,可以为具体的查询生成定制化的机器码,并尽可能的将数据存储在 CPU 的寄存器中进一步加快计算的速度:
LLVM 天然支持 JIT,该技术可以解决条件逻辑冗余的问题;
减少大量的虚函数调用;
将数据尽可能的从内存加载到 Cache 上;
LLVM 做了很多自动矢量化的工作;
比如,下图左侧是通用代码,右侧是 CodeGen 之后的代码。CodeGen 根据实际情况消除了不必要的循环和判断。
![](https://static001.geekbang.org/infoq/f0/f052f14542759f41a6508efc40213981.png)
图 1 通用性处理逻辑和 LLVM 代码示意
另外,LLVM 技术可以有不同的实现粒度。比如:可使用 LLVM 加速表达式计算,或再进一步,将多个算子融合编译成定制的机器码,或将自定义函数、存储过程等编译成定制的机器码。
![](https://static001.geekbang.org/infoq/ef/ef7cc9093922b65d67bb89d4123a4ed4.png)
图 2 LLVM 的实现粒度
数据库在执行引擎中,运用 LLVM 技术提升 SQL 的执行速度。如下图所示:
![](https://static001.geekbang.org/infoq/71/71615dc196f249dfcf960f6dbc46f098.jpeg?x-oss-process=image%2Fresize%2Cp_80%2Fauto-orient%2C1)
图 3 LLVM 技术运用于执行引擎
LLVM 适用场景
LLVM 对所有类型的 SQL 都会有收益吗?
答案是否定的。
因为执行实时编译本身需要耗费一定的时间(简单表达式能做到毫秒级,复杂情况在百毫秒级),对于查询本身耗时较少的场景,加入 LLVM 反而会导致性能劣化。
因此,目前 LLVM 在 OLAP/HTAP 分析型业务场景中收益较大,有着广泛应用,而在 OLTP 交易型业务场景中,则相对没有那么广泛。
LLVM 在 OLTP 中就一定没有收益吗?
答案同样是否定的。
找对场景,一样有收益。比如根据 ISPRAS 2017 年发表的实验结果 jit-compiling sql queries in postgresql using llvm 可知:pgbench 测试下,OLTP 场景中简单的查询加上 JIT(Just-in-time 及时编译,LLVM 天然支持)扩展没有带来性能的提升,甚至将 TPS(事务数/秒)从 21.8 降低到了 7.8。
但是在 Prepared query(plan cached)的情况下,叠加 JIT 技术之后将 TPS 从 20.7 提升到了 43,性能上有了两倍的提升。
GaussDB 中的 LLVM
1. LLVM 在华为应用于数据库的时间线
华为云数据库在 LLVM 上的研究还是非常超前的。早在 2015 年,华为就作为某流行开源数据库社区的全球开发者大会的赞助商,在会上发表的动态编译(Go Faster with Native Compilation)演讲,引起了很大的反响。
当时社区领袖 Josh Burkus 在其博客里面,用一节篇幅专门详细介绍了华为动态编译的议题。
![](https://static001.geekbang.org/infoq/e0/e0c59a06d56e05673191e9a94a188141.png)
图 5 2015 年社区领袖 Josh Burkus 介绍华为的动态编译议题
在 2017 年,华为在面向 OLAP 场景的数据库内核中突破了 LLVM 动态编译技术,并在多个运营商、金融证券的 POC 项目中帮助客户提升数据处理性能,同时,在软件开发过程中充分模块化、通用化接口设计,将 LLVM 同年落地到面向 OLTP 的数据库设计中。
目前,GaussDB 数据库对于 LLVM 也在不断的演进开发。
2. GaussDB LLVM 实现简析
GaussDB 针对向量化引擎(主要用于分析场景)、行存(主要用于交易场景)都实现了 CodeGen。如下图所示,从代码模块层次来看:
1) GaussDB 通过 API 接口层封装处理了 LLVM 环境、资源、基本元素等。
2) GaussDB 在 CodeGen 层调用 API 接口进行了不同粒度的实现。
3) GaussDB 在执行引擎侧根据情况使用 CodeGen 技术进行性能优化。
![](https://static001.geekbang.org/infoq/13/136bdee874f54498c6ec2f777dfd8ffa.jpeg?x-oss-process=image%2Fresize%2Cp_80%2Fauto-orient%2C1)
图 6 GaussDB LLVM 模块层次图
GaussDB 启动后会进行 LLVM 的初始化工作,检查 CPU 对 CodeGen 的支持情况,并进行环境初始化。
在执行启动阶段,以表达式为例,程序会判断当前表达式是否可 JIT,是的话,则会进行 IR 函数的生成和生成定制机器码,及原本表达式执行函数的入口替代工作。
在实际执行过程中,运行处理函数(该函数已经在上一阶段进行了入口替代)进行实际执行工作。
在执行结束后的清理阶段,释放 LLVM 相关资源。
![](https://static001.geekbang.org/infoq/58/5844acefee857bdc852241be5b51a2bd.jpeg?x-oss-process=image%2Fresize%2Cp_80%2Fauto-orient%2C1)
图 7 GaussDB CodeGen 编译执行流程简图
GaussDB 使用了阈值 codegen_cost_threshold 来估算当前查询使用 LLVM 技术是否能带来收益。如果处理数据的规模大于该阈值后,才会继续使用 LLVM 技术进行相关处理。该阈值代表行数,也可以理解成处理数据的规模,默认值为 100000 行,可以调节。
在 OLAP 场景中,GaussDB 在判断是否能够对于一个算子进行 CodeGen 后(如:数据类型,算子类型判断等),开始生成对应的 IR bytecode 片段,之后 MCJIT 模块会调用生成的 LLVM Module 单元进行执行。
在 OLTP 场景中,GaussDB 则会在 Plan Cache 场景下结合 CodeGen 框架,通过缓存机器码的方式,节省下编译生成中间语言 IR Func 以及优化成机器码的时间,整个过程是异步的。因此,在大量重复查询的场景下,后续的查询也会因为 LLVM 技术而受益。
另外,为了避免行数估计错误而选择 CodeGen 导致性能劣化,GaussDB 还研发了当前业界独有的异步编译功能,即在查询语句确定要使用 CodeGen 的时候,将编译工作转交给后台线程,工作线程在 JIT 函数编译完成前继续使用原始执行逻辑执行,编译完成后,再替换成 JIT 函数执行。
3. GaussDB LLVM 支持加速的场景
支持 LLVM 的表达式:
![](https://static001.geekbang.org/infoq/47/4715bcc376b5b3889d48fdb57da9fa77.jpeg?x-oss-process=image%2Fresize%2Cp_80%2Fauto-orient%2C1)
行存表达式计算支持的数据类型不受限制。
在向量化执行引擎中,仅当表达式出现在 Scan 节点的 filter、Hash Join 节点中的 complicate hash condition,hash join filter,hash join target, Nested Loop 节点中的 filter,join filter, Merge Join 节点的 merge join filter, merge join target, Group 节点中的 filter 表达式时,才会考虑是否使用 LLVM 动态编译优化。
在行存执行引擎中,除一次性的表达式计算外,会考虑为所有算子的 filter 和 Targetlist 表达式都使用 LLVM 动态编译优化。
支持 LLVM 的算子:
Join :HashJoin(仅向量化执行引擎支持)
Agg :HashAgg
Sort(仅向量化执行引擎支持)
其中,HashJoin 算子仅支持 Hash Inner Join,对应的 hash cond 仅支持 int4、bigint、bpchar 类型的比较;HashAgg 算子仅支持针对 bigint、numeric 类型的 sum 及 avg 操作,且 group by 语句仅支持 int4、bigint、bpchar,text,varchar,timestamp 类型操作,同时支持 count(*)聚集操作。Sort 算子仅支持对 int4,bigint,numeric,bpchar,text,varchar 数据类型的比较操作。除此之外,无法使用 LLVM 动态编译优化,具体可通过 explain performance 工具进行显示。
4. GaussDB LLVM 使用建议
GUC 参数:
enable_codegen:控制 LLVM 特性的打开和关闭。目前数据库内核侧默认打开。
codegen_cost_threshold:使用处理行数控制是否开启 codegen,默认为 10000。10000 是通过实验验证得出的优化值,不建议将此值设置的过低。
另外,在开启 LLVM 特性的前提下,建议在允许的条件下尽可能设置较大的 work_mem,如果出现大量下盘,则建议关闭 LLVM 动态编译优化。用户可通过 analysis_options 为 on(LLVM_COMPILE),执行对应查询语句,在 User Define Profiling 中就可以看到 LLVM 的编译时间。结合此数据,可对 codegen_cost_threshold 进一步调整以获取更好的查询性能。
5. GaussDB LLVM 性能表现
GaussDB 实验室分别就 codegen 打开和关闭进行了 TPCH 性能测试。
![](https://static001.geekbang.org/infoq/11/11d81fd3b6234f239b6de1302da790f6.png)
表 1 测试环境
测试结果显示,打开 codegen 时,带有 qual 的 SQL,查询性能都有明显提升,且提升比例与 qual 在整个 SQL 中的占比相关,像 Q6、Q12、Q19 等 qual 占比较高的查询,性能提升也较多。
![](https://static001.geekbang.org/infoq/74/741a42dbe11b48cb59594effeaecaee3.png)
表 2 TPCH 部分 Query 的测试结果
TPCC 的性能提升并没有 TPCH 那么多,但据实验室数据,打开 codegen 后,tpmC 提升了约 7%。
总结
LLVM 被广泛运用于数据库、大数据、AI 等领域。在数据库领域,多家商业数据库和开源数据库都应用其加速数据库处理。GaussDB 作为企业级的数据库,经过了多年的技术发展,具备丰富的技术特性,使用该技术后提升了系统的查询性能,使得客户在 OLAP 和 OLTP 多场景中均受益。
“【分享集赞回帖赢好礼】LLVM 技术在 GaussDB 等数据库中的应用”活动来啦!参与就有机会赢取华为背包、华为云定制短袖、蓝牙音箱、书籍等好礼!
活动时间:2024/5/28-6/17
如何参与?
点击链接:https://bbs.huaweicloud.com/forum/thread-0249152272038530018-1-1.html
![](https://static001.geekbang.org/infoq/7b/7bd64c5c0062df0a80f73dc2e8a7ac87.jpeg?x-oss-process=image%2Fresize%2Cp_80%2Fauto-orient%2C1)
版权声明: 本文为 InfoQ 作者【华为云开发者联盟】的原创文章。
原文链接:【http://xie.infoq.cn/article/2e21922d43984e9fce669c7f7】。文章转载请联系作者。
评论