查询执行引擎:如何让聚合计算加速

147 阅读3分钟

查询执行引擎是否高效与其采用的模型有直接关系,模型主要有三种:火山模型、向量化模型和代码生成。

火山模型

在火山模型中,一个查询计划会被分解为多个代数运算符(Operator)。每个 Operator 就是一个迭代器,都要实现一个 next() 接口,通常包括三个步骤:

  1. 调用子节点 Operator 的 next() 接口,获取一个元组(Tuple);
  2. 对元组执行 Operator 特定的处理;
  3. 返回处理后的元组。

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

但是它的缺点也非常明显,主要是两点:

  1. 虚函数调用次数过多,造成 CPU 资源的浪费。
  2. 数据以行为单位进行处理,不利于发挥现代 CPU 的特性。

在火山模型中,处理一个元组最少需要调用一次 next() 函数,这个 next() 就是虚函数。这些函数的调用是由编译器通过虚函数调度实现的;虽然虚函数调度是现代计算机体系结构中重点优化部分,但它仍然需要消耗很多 CPU 指令,所以相当慢。

向量化:TiDB&CockroachDB

向量化模型是由一系列支持向量化运算的 Operator 组成的执行模型。

它和火山模型的唯一区别就是 Operator 的 next() 函数每次返回的是一个向量块,而不是一个元组。向量块是访问数据的基本单元,由固定的一组向量组成,这些向量和列 / 字段有一一对应的关系。

向量处理背后的主要思想是,按列组织数据和计算,充分利用 CPU,把从多列到元组的转化推迟到较晚的时候执行。

总的来说,向量化执行模型对火山模型做了针对性优化,在以下几方面有明显改善:

  • 减少虚函数调用数量,提高了分支预测准确性;
  • 以向量块为单位处理数据,利用 CPU 的数据预取特性,提高了 CPU 缓存命中率;
  • 多行并发处理,发挥了 CPU 的并发执行和 SIMD 特性。

代码生成:OceanBase

代码生成就是按照一定的策略,通过即时编译(JIT)生成代码可以达到类似手写代码的效果。

此外,代码生成是一个推送执行模型(Push Based),这也有助于解决火山模型嵌套调用虚函数过多的问题。与拉取模型相反,推送模型自底向上地执行,执行逻辑的起点直接就在最底层 Operator,其处理完一个元组之后,再传给上层 Operator 继续处理。

代码生成消除了火山模型中的大量虚函数调用,让大部分指令可以直接从寄存器取数,极大地提高了 CPU 的执行效率。


此文章为6月Day21学习笔记,内容来源于极客时间《分布式数据库30讲》