向量化执行和编译执行是目前主流的两种数据库执行引擎优化手段,本文从以下几个方面对向量化执行和编译执行进行浅析。
一、以当代CPU主要特性为背景,引出数据库执行引擎的主要优化方向。
二、分别解析向量化执行和编译执行的原理,并进行对比(结论主要来自2018VLDB论文[1])。
三、介绍了以ROF[2]为代表的向量化与编译执行融合的技术。
当代CPU特性
了解CPU特性可以让我们真正理解各种数据库执行引擎优化技术的动机。影响数据库执行引擎执行效率的CPU特性主要有以下几点:超标量流水线与乱序执行、分支预测、多级存储与数据预取、SIMD。
超标量流水线与乱序执行
CPU指令执行可以分为多个阶段(如取址、译码、取数、运算等),流水线的意思是指一套控制单元可以同时执行多个指令,只是每个指令处在不同的阶段,例如上一条指令处理到了取数阶段,下一条指令处理到了译码阶段。超标量的意思是指一个CPU核同时有多套控制单元,因此可以同时有多个流水线并发执行。CPU维护了一个乱序执行的指令窗口,窗口中的无数据依赖的指令就会被取来并发执行。
程序做好以下两个方面,可以提高超标量流水线的吞吐(IPC,每时钟周期执行指令数)。一,流水线不要断,不需要等到上一条指令执行完,就可以开始执行下一条指令。这意味着程序分支越少越好(知道下一条指令在哪)。二,并发指令越多越好。指令之间没有依赖,意味着更流畅的流水线执行,以及在多个流水线并发执行。
分支预测
程序分支越少,流水线效率越高。但是程序分支是不可避免的。程序分支可以分为两种,条件跳转和无条件跳转。条件跳转来自if或switch之类的语句。无条件跳转又可根据指令的操作数为跳转地址还是跳转地址指针,分为直接跳转和间接跳转。直接跳转一般为静态绑定的函数调用,间接跳转来自函数返回或虚函数之类动态绑定的函数调用。
当执行一个跳转指令时,在得到跳转的目的地址之前,不知道该从哪取下一条指令,流水线就只能空缺等待。为了提高这种情况下的流水线效率,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也是不少数据库的优化方向,