在内存数据库中提高吞吐量的唯一方式就是减少执行指令数目。
大多代码都是为了人能理解而不是单为了性能。
接下来从逻辑查询方面进行分析。
看下面的例子
![](https://img.haomeiwen.com/i11576561/649ecbb1d03aea24.png)
根据这个例子可以得到一个查询树和查询计划。
![](https://img.haomeiwen.com/i11576561/4b7f229211278c7b.png)
这里的查询语句被优化器进行重写,重写成Join。查询树和查询计划很好理解,但是对于CPU而言很不友好,过多的结构和分支,无论for还是if都会产生大量的分支,导致CPU要不断刷新管道和缓存;大量的函数调用导致CPU在内存总不断跳跃。
![](https://img.haomeiwen.com/i11576561/8f7b5a27b73b515b.png)
在执行B.val = ? +1时,有参数输出,执行这一条语句需要传入参数、当前元组、当前元组所在表的格式,需要大量的函数调用,代价很大。
Code Specialization
一个 解决方式就是code specialization,产生专门针对DBMS task的代码来较少指令数量。
对于上面的操作而言,都是一些通用的查询编译过程,支持大多数查询。但是如果是不同的输入具有相似的执行模式,可以在本地编译任何数据库的CPU密集型实体,即对一些特殊的查询进行特定的编译设计。
访问方法
存储过程
操作员执行
谓词评估
记录操作
这样做的好处是
- 属性类型是先验已知的。
数据访问函数调用可以转换为内联指针转换。
即,可以直接通过offset访问元组中的某个属性。 - 谓词是先验已知的。
可以使用原始数据直接比较来评估它们。 - 循环中没有函数调用
允许编译器高效地将数据分发到寄存器并增加缓存重用。
因为没有函数调用,所以只需要直接比较内存块的几个offset的某个size的数据的关系即可。
Code Generation 代码生成
两种方法
-
移植
把关系查询计划转化为命令语言源码(C/C++),然后再用传统编译器来产生本地码 -
JIT编译(LLVM)
生成可快速编译为本地码的查询的中间表示(IR)
HIQUE - Code Generation
对于一个给定的查询计划,产生一个C/C++程序来实现查询的执行(将所有的谓词和类型转换都固定下来)。用线程的编译器把代码转化为一个共享的对象,将起链接到DBMS的进程中(类似于C++写的python库),然后调用exec函数(在一个进程中启动另一个程序执行的方法)
对于查询计划的特定部分,通过算法将这部分进行重新编译,并连接到最终的查询程序中。
生成的查询代码的组件可以调用DBMS中任何其他函数,这允许它使用与Interpreted Plan相同的组件:并发控制、检查点、索引等。
Interpreted Plan 与 Templated Plan
- 需要明确表中的信息,如,属性值大小等。
- 计算每个tuple的大小
- 返回tuple指针
比如对于
Select * from A where A.val=?+1
![](https://img.haomeiwen.com/i11576561/60b212e7ec103f89.png)
在解释的计划中, 其中get_tuple首先要获取从目录中获取表的格式,根据元组大小计算偏移量,然后返回元组的指针;eval()是遍历谓词树获取值,再获取目标属性的偏移量,根据需要进行比较操作,返回true/false。
而对于模板化的计划,已经定义好了像前面解释的计划中的获取表的格式、元组大小这些信息,按照模板执行就行了。
对查询编译性能的评估
- Generic Iterators:通用模型
- Optimized Iterators:对属性值有特定代码生成的模型,即,固定属性值,固定属性值大小
- Generic Hardcoded:对谓词和泛型迭代器产生特定的代码
- Optimized Hardcoded:直接访问元组的评估模型
- HIQUE:对特定查询计划产生代码的评估模型
JIT编译(LLVM)
关系型操作是对查询有效的方式,但不是执行查询最有效率的方式。需要长时间进行编译C/C++ 源代码 成 可执行代码;HIQUE不支持完全的管道,它的管道会因为其他数据没有处理完而等待其他数据。
![](https://img.haomeiwen.com/i11576561/c70dbc4d801350ef.png)
Hyper数据库实现了JIT,用LLVM编译器去编译上面的查询计划,尽可能的将元组留存在CPU的寄存器中。核心部件是低级编程语言IR,不是所有的DBMS都需要用IR实现,LLVM可以调用C++。
它会根据管道瓶颈生成代码,但是多个core或者多个thread都不能同时在一个查询中的多个管道上运行,只能一个线程按顺序在多个管道上完成处理。但是可以安排在管道执行的先后顺序。
网友评论