文章目录

  • 1.当代CPU特性
  • 2.查询执行模型
  • 3.向量化VS编译执行
  • 4.编译执行融合向量化
  • 5.优化方向

1.当代CPU特性

向量化执行和编译执行是目前主流的两种数据库执行引擎优化手段。

了解CPU特性可以让我们真正理解各种数据库执行引擎优化技术的动机。

影响数据库执行引擎执行效率的CPU特性主要有以下几点:

  • 超标量流水线与乱序执行、
  • 分支预测、
  • 多级存储与数据预取、
  • SIMD

超标量流水线与乱序执行

  • CPU指令执行可以分为多个阶段(如取址、译码、取数、运算等);
    (1)流水线的意思是指
    一套控制单元可以同时执行多个指令,只是每个指令处在不同的阶段,例如上一条指令处理到了取数阶段,下一条指令处理到了译码阶段。
    (2)超标量的意思是指
    一个CPU核同时有多套控制单元,因此可以同时有多个流水线并发执行。
    CPU维护了一个乱序执行的指令窗口,窗口中的无数据依赖的指令就会被取来并发执行。
  • 程序做好以下两个方面,可以提高超标量流水线的吞吐(IPC,每时钟周期执行指令数)。
    (1)流水线不要断,不需要等到上一条指令执行完,就可以开始执行下一条指令。这意味着程序分支越少越好(知道下一条指令在哪)。
    (2)并发指令越多越好。指令之间没有依赖,意味着更流畅的流水线执行,以及在多个流水线并发执行。

分支预测

  • 程序分支越少,流水线效率越高。
    但是程序分支是不可避免的。程序分支可以分为两种,条件跳转和无条件跳转。
    (1)条件跳转来自if或switch之类的语句。
    (2)无条件跳转又可根据指令的操作数分为跳转地址还是跳转地址指针,分为直接跳转和间接跳转。
    直接跳转一般为静态绑定的函数调用,间接跳转来自函数返回或虚函数之类动态绑定的函数调用。
  • 当执行一个跳转指令时,在得到跳转的目的地址之前,不知道该从哪取下一条指令,流水线就只能空缺等待。
    为了提高这种情况下的流水线效率,CPU引入了一组寄存器,用来专门记录最近几次某个地址的跳转指令的目的地址。
    这样,当再一次执行到这个跳转指令时,就直接从上次保存的目的地址出取指令,放入流水线。等到真正获取到目的地址的时候,再看如果取错了,则推翻当前流水线中的指令,取真正的指令执行。

多级存储与数据预取

  • 多级存储就不用解释了,当数据在寄存器、cache或内存中,CPU取数速度不在一个数量级。
    尤其cache和内存访问,相差两个数量级。CPU在内存取数的时候会首先从cache中查找数据是否存在。若不存在,则访问内存,在访问内存的同时将访问的数据所在的一个内存块一起载入cache。
  • 如果程序访问数据存在线性访问的模式,CPU会主动将后续的内存块预先载入cache,这就是CPU的数据预取。
    有时候程序访问数据并不是线性的,例如Hash表查找等。CPU也提供了数据预取指令,程序可以事先主动将会用到的数据载入cache,这就是Software Prefetch。
  • 如何利用好寄存器和cache是数据库查询执行非常重要的优化方向。

SIMD

  • 单指令多数据流,对于计算密集型程序来说,可能经常会需要对大量不同的数据进行同样的运算。SIMD引入之前,执行流程为同样的指令重复执行,每次取一条数据进行运算。
    例如有8个32位整形数据都需要进行移位运行,则由一条对32位整形数据进行移位的指令重复执行8次完成。
    SIMD引入了一组大容量的寄存器,一个寄存器包含832位,可以将这8个数据按次序同时放到一个寄存器。同时,CPU新增了处理这种832位寄存器的指令,可以在一个指令周期内完成8个数据的位移运算。

  • 如何利用好SIMD也是不少数据库的优化方向,尤其是向量化执行的策略下。

2.查询执行模型

火山模型

  • 数据库查询执行最著名的是火山模型,也是在各种数据库系统中应用最广泛的模型。

  • SQL查询在数据库中经过解析,会生成一棵查询树,查询数的每个节点为代数运算符(Operator)。
    火山模型把Operator看成迭代器,每个迭代器都会提供一个next() 接口。

  • 一般Operator的next() 接口实现分为三步
    (1)调用子节点Operator的next() 接口获取一行数据(tuple)
    (2)对tuple进行Operator特定的处理(如filter 或project 等)
    (3)返回处理后的tuple。

  • 因此,查询执行时会由查询树自顶向下的调用next() 接口,数据则自底向上的被拉取处理。

  • 火山模型的这种处理方式也称为拉取执行模型(Pull Based)。

  • 火山模型的优点是,处理逻辑清晰,每个Operator 只要关心自己的处理逻辑即可,耦合性低。

  • 火山模型的缺点:
    数据以行为单位进行处理,不利于CPU cache 发挥作用。且每处理一行需要调用多次next() 函数,而next()为虚函数,开销大。
    每次都是计算一个 tuple(Tuple-at-a-time),这样会造成多次调用 next ,也就是造成大量的虚函数调用,这样会造成 CPU 的利用率不高。
    (C++ 中用 virtual 标记的函数,而在 Java 中没有 final 修饰的普通方法(没有标记为 static、native)都是虚函数。)

  • 为了更好地理解一个 Operator 中发生了什么,下面通过伪代码来理解 Select Operator:

Tuple Select::next() {while (true) {Tuple candidate = child->next(); // 从子节点中获取 next tupleif (candidate == EndOfStream) // 是否得到结束标记return EndOfStream;if (condition->check(candidate)) // 是否满足过滤条件return candidate; // 返回 tuple}
}
  • eg:
SELECT Id, Name, Age, (Age - 30) * 50 AS Bonus
FROM People
WHERE Age > 30

对应火山模型如下:

  • User:客户端;
  • Project:垂直分割(投影),选择字段;
  • Select(或 Filter):水平分割(选择),用于过滤行,也称为谓词;
  • Scan:扫描数据。
  • 过程:
    这里包含了 3 个 Operator:
    首先 User 调用最上方的 Operator(Project)希望得到 next tuple;
    Project 调用子节点(Select);
    而 Select 又调用子节点(Scan);
    Scan 获得表中的 tuple 返回给 Select,Select 会检查是否满足过滤条件,如果满足则返回给 Project,如果不满足则请求 Scan 获取 next tuple。
    Project 会对每一个 tuple 选择需要的字段或者计算新字段并返回新的 tuple 给 User。当 Scan 发现没有数据可以获取时,则返回一个结束标记告诉上游已结束。
  • tuple含义:
    元组(tuple)是关系数据库中的基本概念,关系是一张表,表中的每行(即数据库中的每条记录)就是一个元组,每列就是一个属性。 在二维表里,元组也称为行

编译执行

  • 考虑到火山模型大量虚函数调用导致的性能损失,推送执行模型(Push Based)很好的解决了这个问题。与拉取模型相反,推送模型自低向上的执行,执行逻辑由底层Operator开始,其处理完一个tuple之后,将tuple传给上层Operator处理

  • 前面CPU的多级存储介绍提到,数据访问速度最快的是寄存器。
    所以在执行查询树时最理想的情况就是数据一直留在寄存器中(假设寄存器的容量足以放下一个tuple),每个Operator直接处理寄存器中的数据。
    Operator之间从拉取模型的虚函数调用,变成了以数据为中心(data-centric)的顺序执行。

  • 当然,并不是所有的Operator的运算逻辑都可以处理完寄存器中的tuple之后,把tuple留在寄存器中,由下一个Operator 接着处理。
    例如Join的时候,需要构建hash表,tuple就必须写入内存了(整个hash表当然不可能放到寄存器)。

  • [3]论文的思想:
    每个Operator会根据规则拆分为两个代码块,一块对应Produce() ,一块对应consume()。代码生成的时候就可以根据这个规则生成代码。
    Produce()函数负责产生结果tuple;
    Consume()函数负责具体的tuple处理逻辑;
    好处:编译执行以数据为中心,消灭了火山模型中的大量虚函数调用开销。甚至使大部分指令执行,可以直接从寄存器取数,极大的提高了执行效率。

向量化执行

  • 向量化执行依然采用类似火山模型的拉取式模型,唯一的区别是其Operator的next()函数每次返回的是一批数据(如1000行)

  • 一般向量化特指列式存储系统中,按列聚合的一组数据;
    在行式系统中称为RowSet迭代,本文不严格区分这两种情况

  • eg:下图是一个JoinOperator的编译生成的伪代码和向量化执行的伪代码的对比。

  • 图中(a)部分为编译执行模型生成的伪代码
    JoinOperator 的执行逻辑为,以左表的数据构建Hash表,然后以右表中的每行记录,分别去Hash表查找。
    这里的Hash表的冲突处理采用的是链地址法,伪代码中最后一个循环就是遍历链表,找到真正的匹配项。

  • 图中(b)部分向量化执行的伪代码
    与火山模型不同的,它一次处理一组数据。
    因此,可以看到这里面的变量都是Vector。
    由于变量为Vector,就需要事先定义一些专门处理Vector的元语(Primitives):例如为Vector中的每一个元素计算Hash值的proheHash_,以及图中的compareKeys_、buildGather_。

向量化执行模型有一下几个好处:

  • 1.大大减少火山模型中的虚函数调用数量;
  • 2.以块为处理单位数据,提供了cache命中率;
  • 3.多行并发处理,契合了CPU乱序执行与并发执行的特性。
  • 4.同时处理多行数据,使SIMD有了用武之地(虽然目前SIMD对大多数数据库查询起到的作用比较有限[1],本质上数据库查询都属于数据访问密集型应用,而不是SIMD最擅长的计算密集型应用)。

3.向量化VS编译执行

首先这两个模型是不相容的,二者只能取其一。

  • 因为编译执行强调的是以数据为中心,在一个Pipeline内是不会有Materialization的;
    但是向量化执行是拉取模型,每次经过next()调用,Vector的传递必须Materialize

  • Materialization Model,物化

  • [1]的团队在同一个系统(Peloton)里实现了这两种模型,以进行各方面对比。
    [1]选取了TPC-H中非常有代表性的5个SQL查询,Q1和Q18主要运算为定点数运算(Fixed-point arithmetic)和Aggregation,Q6主要运算为Filter,Q3和Q9主要运算为Join;
    从图中可以看出向量化执行对于Q3和Q9查询更高效,而编译执行对于Q1和Q18语句更高效。

  • 原因以及分析:

  • 图中的Typer是编译执行引擎,Tectorwise是向量化执行引擎。Memory Stall指的是CPU执行指令时,内存取数的等待时间。
    (1)可以看出Q3和Q9,Typer主要就是慢在了Memory Stall。因为Hash表数据分布比较随机,Hash查找时cache命中率不高,经常需要访问内存。
    编译执行循环内部会包含多个Operator的运算,这些有依赖关系的指令占据了大部分的乱序执行窗口,并发度低,总的等待取数时间就长了
    向量化执行模型的循环较短,并发度高,可以同时有更多的指令等待取数,总的等待时间就短了。
    更复杂的循环也导致分支预测失败的代价更高。
    (2)对于Q1和Q18,由于运算过程中cache命中率比较高,没有Memory Stall的拖累,编译执行模型Pipeline执行无Materialization的优势就体现出来了

4.编译执行融合向量化

[2]设计了一个原型系统 Relaxed Operator Fusion (ROF)来实现编译执行融合向量化

  • 把查询树分解一下,部分用向量化方式,部分用编译执行方式即可

  • 思想:
    编译执行的主要目标是减少Materialization,ROF则是在编译执行的基础上,主动在其中的Pipeline中插入Materialization,将Pipeline分割为Stage,在Stage内依然是tuple-at-a-time data-centric的推送模型,保留了编译执行数据停留在寄存器中的优点。
    而跨Stage或Pipeline时,则以Block(一组tuple)为单位传递数据,这个时候就可以利用上SIMD。
    另外,ROF还使用了Software Prefetch来优化编译执行当Hash表查找时cache命中率低Memory Stall过多的问题。

  • eg:下面的SQL是TPC-H Q19的简化版本。
    其中CLAUSE1~3分别是LineItem和Part两个表上的查询条件。

SELECT SUM(...) AS revenue
FROM LineItem JOIN Part ON l_partkey = p_partkey
WHERE (CLAUSE1) OR (CLAUSE2) OR (CLAUSE3)

这段SQL对应的编译执行的查询数和ROF的查询树如下图。

  • 可以看到ROF 相比原来的查询树,在P2这个Pipeline的第一个Filter后面插入了一个Operator(图中标红)。
    这个Operator表示Vector Output,把P2分成了两个Stage(我们称这种分割Stage的Operator为Stage boundary),在Stage内部为tuple-at-a-time,跨Stage则以Vector为单位传递数据。

  • P2对应ROF的伪代码如下图。
    Stage1进行TableScan和Filter,将VECTOR_SIZE数量的Tuple插入Vector。
    Stage2对Vector中的数据进行HashProbe、Filter以及Aggregate。

  • 上面的例子展示了ROF是怎么把Pipeline分割成Stage的,但是到目前为止,我们并没有看到这么做有什么好处。

  • 这里最关键的问题是,应该在Pipeline的哪个位置插入Stage boundary,才能达到最优的效果。
    ROF按照两个规则分割Stage:
    R1. 可以使用SIMD的Operator的输入和输出点;
    R2. 需要对无规律地址数据(且数据量大于Cache)进行访问的Operator的输入点。
    R1是为了利用SIMD进行并发计算,R2是为了使用Software prefetch提高cache命中率。
    这是基本策略,在实现时还有一些技术点需要考虑。为了快速获取SIMD寄存器中Filter过后的数据,ROF利用一个Mask索引,将SIMD寄存器中的有效数据Shuffle到一起。
    当数据量不大时,数据预取反而会带来额外开销。ROF在编译时会生成两套执行路径,在运行时根据数据量决定是否需要预取。

5.优化方向

  • 其次,火山模型中一次只取一条数据,如果每次取多条数据呢?
    (1)可以将每次 next 带来的 CPU 开销被一组数据给分摊。这样当 CPU 访问元组中的某个列时会将该元组加载到 CPU Cache(如果该元组大小小于 CPU Cache 缓存行的大小), 访问后继的列将直接从 CPU Cache 中获取,从而具有较高的 CPU Cache 命中率
    (2)当然是同一列的时候,所以针对的是列存的场景,因为输入是同列的一组数据,面对的是相同的操作,这正是向量寄存器干的事情,这是 CPU 层面计算性能的优化,因此称为向量化。
    并且如果每次只取一列的部分数据,返回一个可以放到 CPU Cache 的向量,那么又可以利用到 CPU Cache。

  • 向量化
    向量化计算就是将一个循环处理一个数组的时候每次处理 1 个数据共处理 N 次,转化为向量化——每次同时处理 8 个数据共处理 N/8 次,其中依赖的技术就是 SIMD(Single Instruction Multiple Data,单指令流多数据流),SIMD 可以在一条 CPU 指令上处理 2、4、8 或者更多份的数据。

CPU 层面的优化方案的结论

  • 相比火山模型,编译执行和向量化都使数据库查询执行性能得到了极大的提升,这两者之间相比又如何呢。
    首先这两个模型是不相容的,二者只能取其一。
    因为编译执行强调的是以数据为中心,在一个 CPU Pipeline 内是不会有物化的;
    向量化执行是拉模型,每次经过 next() 调用,向量的传递必须要进行物化。

  • 相关论文
    [1] Kersten, Timo, et al. “Everything you always wanted to know about compiled and vectorized queries but were afraid to ask.” Proceedings of the VLDB Endowment 11.13 (2018): 2209-2222.
    [2] Menon, Prashanth, Todd C. Mowry, and Andrew Pavlo. “Relaxed operator fusion for in-memory databases: Making compilation, vectorization, and prefetching work together at last.” Proceedings of the VLDB Endowment 11.1 (2017): 1-13.
    [3] Neumann, Thomas. “Efficiently compiling efficient query plans for modern hardware.” Proceedings of the VLDB Endowment4.9 (2011): 539-550.
    [4] Boncz, Peter A., Marcin Zukowski, and Niels Nes. “MonetDB/X100: Hyper-Pipelining Query Execution.” Cidr. Vol. 5. 2005.

  • 参考:SQL 优化之火山模型,向量化与编译执行浅析

SQL优化之火山模型、向量化、编译执行相关推荐

  1. oracle试图执行计划,Oracle SQL优化与调优之显示执行计划(上)

    通过查询语句显示计划 通过查询语句从这些视图里面读出执行计划并作格式化输出的方法都非常相似,我们这里以 sql_plan 为例给出示例. 通过包 DBMS_XPLAN 显示计划 这个包可以根据我们选择 ...

  2. oracle层级计算推演,(特价书)Oracle 高性能SQL引擎剖析:SQL优化与调优机制详解(资深Oracle专家黄玮十年磨一剑,盖国强作序力荐)(china-pub首发)...

    第一篇 执 行 计 划 执行计划是指示Oracle如何获取和过滤数据.产生最终结果集,是影响SQL语句执行性能的关键因素.我们在深入了解执行计划之前,首先需要知道执行计划是在什么时候产生的,以及如何让 ...

  3. 从一条巨慢SQL看基于Oracle的SQL优化(重磅彩蛋+PPT)

    本文根据DBAplus社群第110期线上分享整理而成,文末还有好书送哦~ 讲师介绍 丁俊 新炬网络首席性能优化专家 SQL审核产品经理 DBAplus社群联合发起人.<剑破冰山-Oracle开发 ...

  4. SQL性能优化-查询条件与字段分开执行,union代替in与or,存储过程代替union

    PS:概要.背景.结语都是日常"装X",可以跳过直接看优化历程 环境:SQL Server 2008 R2.阿里云RDS:辅助工具:SQL 审计 概要 一个订单列表分页查询功能,单 ...

  5. 《高性能SQL调优精要与案例解析》一书谈主流关系库SQL调优(优化TUNING)技术精髓之——执行计划获取及理解

    <高性能SQL调优精要与案例解析>中,主要以Oracle为样本讲解了SQL调优(优化&TUNING),其中,前面博文中本人也说到,就SQL调优的思路.方法和步骤来说,各关系库几乎是 ...

  6. 《Oracle高性能SQL引擎剖析:SQL优化与调优机制详解》一1.1 生成执行计划

    1.1 生成执行计划 在Oracle中,任何一条语句在解析过程中都会生成一个唯一的数值标识,即SQL_ID.而同一条语句,在解析过程中,可能会因为执行环境的改变(例如某些优化参数被改变)而生成多个版本 ...

  7. [转]Mysql中的SQL优化与执行计划

    From : http://religiose.iteye.com/blog/1685537 一,如何判断SQL的执行效率? 通过explain 关键字分析效率低的SQL执行计划. 比如: expla ...

  8. 面试官:不会看 Explain执行计划,简历敢写 SQL 优化?

    来自:程序员内点事 昨天中午在食堂,和部门的技术大牛们坐在一桌吃饭,作为一个卑微技术渣仔默默的吃着饭,听大佬们高谈阔论,研究各种高端技术,我TM也想说话可实在插不上嘴. 聊着聊着突然说到他上午面试了一 ...

  9. 正确使用索引(sql优化),limit分页优化,执行计划,慢日志查询

    查看表相关命令 - 查看表结构 desc 表名- 查看生成表的SQL show create table 表名- 查看索引 show index from 表名 使用索引和不使用索引 由于索引是专门用 ...

最新文章

  1. 【踩坑之旅】-webpack (v4.8.1) + vue-cli (v2.5.3)升级准备
  2. 领取Virmach免费Minecraft主机搭建“我的世界”服务器
  3. iOS原生WebView中JavaScript和OC交互
  4. 【手势交互】9. PS Move
  5. liunx的urandom生成随机字符
  6. (68)FPGA面试题-使用不同的代码实现2:1 MUX ?使用assign语句
  7. python内建函数(不完全)
  8. Visual Studio.NET 2008开发一册通--LINQ从基础到项目实战这本书怎么样
  9. iPhone4S安装Linux系统,别失望太早 iPhone 4S其实就是iPhone 5
  10. 如何获取类(接口)的成员
  11. SQL Server编写存储过程小工具(三)
  12. 视频教程-【曾贤志】Excel函数视频教程-Office/WPS
  13. 输出流创建txt文件
  14. 【Latex】PPT画图,导出emf格式,word插入emf文件并导出pdf,pdf裁剪并导出eps文件,latex插入eps文件
  15. oracle中两个日期相减
  16. 射频识别技术软硬件系统研制
  17. NEXTCHIP概述-2
  18. 【离散数学】关于欧拉图与哈密顿图的讨论
  19. 第六讲 双指针、BFS与图论、单链表
  20. 摄影测量学数字测量实习:单模型4D生产与空中三角测量的理论与实践

热门文章

  1. 视频教程-微信小程序项目实战:高仿iOS计算器-微信开发
  2. 不可用来编辑html的文件,上网浏览器主页无法修改的解决办法
  3. Excel公式与函数的应用-Excel学习记录-5.1/2/3/4/5
  4. 语音转文字转换器如何使用
  5. 仿抖音 APP 视频切换和点赞效果
  6. 实战:活动运营的最高境界 产品活动化活动产品化
  7. chkdsk /f P:很强大,帮我解决了硬盘写保护的问题
  8. 区块链会议_重构世界·2018区块链新经济杭州峰会_20181119
  9. 尤里的复仇II 回归
  10. 设计模式:结构型模式-桥接、外观、组合、享元模式