解密数据仓库LLVM技术神奇之处
摘要:广义上讲就是指LLVM本身,它是一套用于开发编译前端与后端的工具套件,狭义上讲LLVM就是指整个编译套件的优化器及后端,而CLANG可以认为是C/C++的前端。
本文分享自华为云社区《GaussDB(DWS) 性能黑科技之LLVM技术解密》,作者:清道夫。
1.LLVM是什么?
LLVM这个名字最早源于底层虚拟机(Low Level VirTual Machine)的首字母缩写,但随着LLVM项目的演进,底层虚拟机的含义已经不再适用于LLVM。现在谈到LLVM,广义上讲就是指LLVM本身,它是一套用于开发编译前端与后端的工具套件,狭义上讲LLVM就是指整个编译套件的优化器及后端,而CLANG可以认为是C/C++的前端。
2.LLVM的优势?
传统编译器最常见的三阶段设计:
前端:解析源代码生成抽象语法树
优化器:根据优化规则对代码进行改善,相当于规则重写,例如消除冗余计算等
后端:将代码映射到目标指令集上,包括指令选择、寄存器分配和指令调度等。
GCC是一个完整的可执行文件,没有为其他语言的开发者提供代码重用的接口,灵活性不足。
LLVM也采用经典的三段式设计,但与传统编译器最大的不同就是针对不同语言都提供了同样一种中间表示IR以及模块化的后端(MCJIT模块可以支持JIT编译)。
3.DWS为什么要使用LLVM?
为具体的查询生成定制化的机器码代替通用的函数实现,并尽可能的将数据存储在CPU的寄存器中:
(1)解决条件逻辑冗余的问题,需要用到JIT(jiust-in-time)编译技术,LLVM天然支持JIT技术。
(2)减少大量虚函数调用
(3)改善数据调用,将数据尽可能的从内存加载到cache上
(4)发挥通用硬件平台的扩展指令集功能,例如SSE4.2
一个简单的例子,可以参照下面的优化前伪代码片段:
(1)首先是优化前的代码片段(a):
可以看到,在物化tuple的过程中,需要根据不同的列属性判断其偏移量,并调用相应的解析函数。
(2)借助LLVM使用JIT编译技术后,可以生成如下优化后的伪代码片段(b):
其中偏移量已经被提前计算出来,且无需判断列属性既可以调用对应的解析函数。减少了偏移量的重复计算及类型的重复判断。
优化前的代码(a):
void MaterializeTuple(char *tuple) { for (int i = 0; i < num_slots; ++i) { char *slot = tuple + offset[i]; switch(types[i]) { case BOOLEAN: *slot = ParseBoolean(); break; case INT: *slot = ParseInt(); break; case FLOAT:... case STRING:... // etc. } } }
优化后的代码(b):
void MaterializeTuple(char *tuple) { *(tuple + 0) = ParseInt(); *(tuple + 4) = ParseBoolean(); *(tuple + 5) = ParseInt(); }
4.如何使用LLVM
在DWS中,涉及LLVM的GUC参数有两个:
(1)enable_codegen:总开关,用于控制是否开启codegen,默认为on
(2)codegen_cost_threshold:使用处理行数控制是否开启codegen,默认门槛值为10000。
目前DWS中并非是通过计划代价去控制是否开启codegen,而是通过处理行数来控制的。此处10000是通过实验验证得出的优化值,不建议将此门槛值设置的过低,因为代码执行过程中的即时编译是有代价的。
简单示例:
test=# create table llvm_test(a int,b int)with(orientation=column); NOTICE: The 'DISTRIBUTE BY' clause is not specified. Using 'a' as the distribution column by default. HINT: Please use 'DISTRIBUTE BY' clause to specify suitable data distribution column. CREATE TABLE test=# insert into llvm_test values(generate_series(1,10),generate_series(1,10)); INSERT 0 10 test=# show enable_codegen; enable_codegen ---------------- on (1 row) test=# show codegen_cost_threshold; codegen_cost_threshold ------------------------ 10000 (1 row) test=# set codegen_cost_threshold=0; --为了简化,将门槛值设置为0 SET test=# set enable_fast_query_shipping=off; --关闭FQS,便于打印DN执行信息 SET test=# explain performance select * from llvm_test where b<10; --简单表达式会使用codegen Datanode Information (identified by plan id) --------------------------------------------------------------------------------------------------------------------- 1 --Row Adapter (actual time=14.929..15.393 rows=9 loops=1) (CPU: ex c/r=5783, ex row=9, ex cyc=52048, inc cyc=46174324) 2 --Vector Streaming (type: GATHER) (actual time=14.920..15.372 rows=9 loops=1) (Buffers: 0) (CPU: ex c/r=5124697, ex row=9, ex cyc=46122276, inc cyc=46122276) 3 --CStore Scan on public.llvm_test LLVM Optimized datanode1 (actual time=0.094..0.141 rows=9 loops=1) (filter time=0.002) (RoughCheck CU: CUNone: 1, CUSome: 9) datanode1 (Buffers: shared hit=26 dirtied=1) datanode1 (CPU: ex c/r=41430, ex row=10, ex cyc=414306, inc cyc=414306)
此处仅截取部分执行信息,在Datanode Information中的扫描算子中有LLVM Optimized信息,代表已经使用了JIT编译。
如果想查看LLVM JIT编译的时间耗时,可以借助GUC参数analysis_options进行设置后,执行对应查询语句,在User Define Profiling中就可以看到LLVM的编译时间。
test=# set analysis_options='on(LLVM_COMPILE)'; SET test=# explain performance select * from llvm_test where b<10;
此处仍使用之前用例的设置,不再赘述。
User Define Profiling ---------------------------------------------------------------- Plan Node id: 2 Track name: coordinator get datanode connection coordinator1: (time=0.015 total_calls=1 loops=1) Plan Node id: 3 Track name: load CU description datanode1 (time=0.061 total_calls=10 loops=1) Plan Node id: 3 Track name: min/max check datanode1 (time=0.008 total_calls=10 loops=1) Plan Node id: 3 Track name: fill vector batch datanode1 (time=0.032 total_calls=9 loops=1) Plan Node id: 3 Track name: apply projection and filter datanode1 (time=0.005 total_calls=9 loops=1) Plan Node id: 3 Track name: LLVM Compilation datanode1 (time=11.024 total_calls=1 loops=1) Plan Node id: 3 Track name: fill later vector batch datanode1 (time=0.000 total_calls=9 loops=1)
其中LLVM Compilation即为LLVM的即时编译的时间代价。此处编译的时间代价通常会与SQL执行流程的复杂程度成正比关系,在实际的调优实践中可以结合此数据对处理数据行数的门槛值做进一步的调整。
5.LLVM适用场景
目前LLVM仅支持DN上且是列存向量化执行路径的查询作业,其支持的表达式及算子如下:
- 支持LLVM的表达式
- Case…when… 表达式
- In表达式
- Bool表达式 (And/Or/Not)
- BooleanTest表达式 (IS_NOT_KNOWN/IS_UNKNOWN/IS_TRUE/IS_NOT_TRUE/IS_FALSE/IS_NOT_FALSE)
- NullTest表达式 (IS_NOT_NULL/IS_NULL)
- Operator表达式
- Function表达式 (lpad, substring, btrim, rtrim, length)
- Nullif表达式
表达式计算支持的数据类型包括bool, tinyint, smallint, int, bigint, float4, float8, numeric, date, time, timetz, timestamp, timestamptz, interval, bpchar, varchar, text, oid。
仅当表达式出现在向量化执行引擎中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动态编译优化。
- 支持LLVM的算子:
- Join :HashJoin/SonicHashJoin
- 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工具进行显示(如上用例所示)。
想了解GuassDB(DWS)更多信息,欢迎微信搜索“GaussDB DWS”关注微信公众号,和您分享最新最全的PB级数仓黑科技,后台还可获取众多学习资料哦~

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
仪表板展示 | 用DataEase可视化分析2022年北京冬奥会数据
2022年北京冬奥会刚刚落下帷幕,我作为一个奥运迷,既为能够无时差享受比赛感到满足,也为我们国家能够圆满地举办感到骄傲。看完闭幕式,我就想如何能够以自己的方式来纪念这一届意义非凡的冬奥会,用可视化的方式来展示冬奥会的成绩和成果。 我在网上找了很多数据可视化的工具,经过多方比较和选择,最后选择了DataEase开源数据可视化分析平台 v1.7.0版本。DataEase的口号是“人人可用的开源数据可视化分析工具”。这个项目附带有完整的指导文档和手册,有解决不了的问题还可以加入开源群询问各路大神,特别适合我这种什么都不会的小白。 以下是我最终选择使用DataEase进行数据可视化分析的主要原因: ■开源是最大的亮点。目前市面上很多数据分析软件,不少都是要收费的,因此完全免费的开源系统非常适合我这种使用频率不高的人; ■支持多种数据源,比如数据库、Excel表格等; ■制作简单,在制作的过程中,以拖拉拽的方式生成图表,操作简单易上手; ■有丰富的组件元素,可以在仪表板上插入文本、形状、时间、图片和视频等。 确定主题 首先我制作了一个思维导图,确定我想要展示的内容。冬奥主题仪表板的内容包括: ■...
- 下一篇
今儿直白的用盖房子为例,给你讲讲Java建造者模式
摘要:建造者模式(Builder Pattern)又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。 本文分享自华为云社区《【Java设计模式】用盖房子案例讲解建造者模式(生成器模式)》,作者: 我是一棵卷心菜 。 现在我们需要建房子,过程为打桩、砌墙、封顶。房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是要求不要相同的。请编写程序,完成需求 传统方式 1、类图分析 2、代码分析 先创建一个抽象类AbstractHouse,具有打地基、砌墙、封装屋顶以及建房子的功能 public abstract class AbstractHouse { //打地基 public abstract void buildBasic(); //砌墙 public abstract void buildWalls(); //封装屋顶 public abstract void roofed(); //开始建房子 public void build() { buildBasi...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Hadoop3单机部署,实现最简伪集群
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- Windows10,CentOS7,CentOS8安装Nodejs环境
- SpringBoot2全家桶,快速入门学习开发网站教程