introduction
Vectorized query execution is a Hive feature that greatly reduces the CPU usage for typical query operations like scans, filters, aggregates, and joins. A standard query execution system processes one row at a time. This involves long code paths and significant metadata interpretation in the inner loop of execution. Vectorized query execution streamlines operations by processing a block of 1024 rows at a time. Within the block, each column is stored as a vector (an array of a primitive data type). Simple operations like arithmetic and comparisons are done by quickly iterating through the vectors in a tight loop, with no or very few function calls or conditional branches inside the loop. These loops compile in a streamlined way that uses relatively few instructions and finishes each instruction in fewer clock cycles, on average, by effectively using the processor pipeline and cache memory。
向量化查询执行是Hive特性,可以大大减少典型查询操作(如扫描,过滤器,聚合和连接)的CPU使用率。一个标准的查询执行系统一次处理一行。这涉及在执行的内部循环中长的代码路径和重要的元数据解释。目前Hive也严重依赖于惰性的反序列化,数据列通过一层对象检查器来识别列类型,反序列化数据并在内部循环中确定合适的表达式例程。这些虚拟方法调用层进一步减慢了处理速度。向量化的查询执行通过一次处理1024行的数据块来简化操作。在块内,每一列都被存储为一个向量(一个基本数据类型的数组)。算术和比较等简单操作是通过在紧密循环中快速迭代向量来完成的,在循环内没有或很少有函数调用或条件分支。这些循环以简化的方式进行编译,使用相对较少的指令,并通过有效地使用处理器流水线和高速缓存存储器,以较少的时钟周期完成每条指令。
参考:
Vectorized+Query+Execution
HIVE-4160
两个有点相关的
SIMD
单指令单数据(SISD)的CPU对加法指令译码后,执行部件先访问内存,取得第一个操作数;之后再一次访问内存,取得第二个操作数;随后才能进行求和运算。而在SIMD型的CPU中,指令译码后几个执行部件同时访问内存,一次性获得所有操作数进行运算。这个特点使SIMD特别适合于多媒体应用等数据密集型运算。
SIMD
如图中所示,对于8个数做加法,在SISD中,需要load数据8次,add 4次,最后再save 4次,而在SIMD架构中,只需要load数据2次,add 1次,save 1次,所以SIMD型CPU对数据密集型运算优势很大。
Runtime Code Generation
我在研究impala中看到了impala使用了一项code generation的技术,在spark 1.5开始也逐步引入codeGen。下面具体讲讲code generation。
这一切的基础在于最优的查询引擎一定是原生的应用,因为它们针对你的数据格式而开发,而且仅仅支持你的查询。举个例子,这是一个理想的代码:
select count(*)
from tbl
where col like %XYZ%
这与 grep -c "XYZ" tbl 的效率一样高。
另一个例子,select sum(col) from tbl。如果表格只有一个int64的列,使用little endian编码,这可以通过专用的应用来运行:
int64_t sum = 0;
int64_t* values = (int64_t*)buffer;
for (int i = 0; i < num_rows; ++i) {
sum += values[i];
}
这两个查询都是十分合理的(因为第二段代码用于列式的数据),不过在运行已有的查询引擎时会变得缓慢。(这是假设蛮力的执行。数据库当然可以使用索引或保存预先计算的值来执行比简单的应用程序更好的性能。当然,这里描述的技术也将被应用于非暴力执行策略)因为目前使用的大多数查询引擎都遵循一种解释性的方法,增加了各种形式的执行开销。
增加的运行成本来自:
- 调用虚函数。没有编译就解释执行表达式(例如col1 + col2 < col3),致使在每个表达式上产生虚函数调用。(这当然依赖于安装启用,但是我们,也可能包括大多数其它人采用一种类似“Eval”函数,每一个操作符都会生效。)在这种情况下,表达式自身占用资源很少,但虚函数调用的资源占用是很多的。
- 各种类型的大的代码分支判断,操作符,以及没有被查询引用的函数。分支预测器可以缓和这类问题,但同时分支指令会阻止流水线的效率以及指令集的并行性。
- 不能传送所有的常量。Impala能计算一个固定宽度的元组格式(列3字节偏移值为16)。好处是这些常量不用重复写入代码,而不用在内存中去查找。
生成代码的目标就是让每个查询都使用同样数量的指令,就像定制代码一样,因为查询执行支持广泛的功能, 工具可以精确的匹配查询,而并不需要额外的资源。
参考:
impala-code-generation
总结
向量化查询执行是一种查询优化策略,主要目的是降低CPU处理数,通过batch load,以及在对确定的列上面使用特点的执行方法,避免了if-else-then等分支判断,以及虚函数的调用,这些都能够降低CPU的使用率,也就加快了查询效率。
网友评论