1. 迭代器模型(Volcano Model)与向量化执行模型的工程边界,传统火山模型的虚函数开销如何被向量化消除?
请说明迭代器模型(Volcano/火山模型)与向量化执行模型各自的工程边界,以及传统火山模型逐行调用虚函数(next())的开销,向量化执行是如何把它消除的?
- 火山模型逐行迭代时虚函数调用和数据搬运的开销来源
- 向量化执行批量处理、按列循环、减少虚函数调用与指令开销
- 两者在 OLTP 与 OLAP 场景的适用边界
火山模型用一个统一的 next() 接口把每个算子串成树,每次调用返回一行,下层算子的结果通过内存中的行数据结构逐行向上传递。这个模型功能正交、容易实现,但代价是:(1) 每个算子求值一次只处理一行,产生海量的虚函数调用(造成 CPU 分支预测失败、函数指针间接跳转);(2) 每一行都要经过内存分配、函数调用栈、数据拷贝,指令级无法向量化,CPU 大部分时间花在循环控制和调度上而非真正的计算。向量化执行直接把算子改为批量处理一批行(通常 1024 行的 Batch),把循环从"行"提升到"列",并且在最内层循环对同一列连续数据做 SIMD 批处理,从而消除虚函数开销、改善缓存局部性、一次加载多元素并行计算。工程边界上,OLTP 场景行数少、单次查询处理行的混合逻辑复杂、需要随机访问和交互,火山模型简单且足够;OLAP 场景动辄扫描上亿行、列式存储天然按列组织,向量化能成数量级提升吞吐。
关键不是"逐行"本身,而是逐行导致的控制流开销(虚函数调用、分支、缓存失效)无法被 CPU 的指令级并行和 SIMD 利用。向量化把内循环变成对连续列的紧循环,让编译器和硬件都能优化。
这里用一段伪代码示意火山模型与向量化内循环的差异。
-- 火山模型示意:逐行求值
while (row = next()) do
result = evaluate_filter(row); -- 每行一次函数调用
emit(row);
-- 向量化示意:对整批列做 SIMD 紧循环
for (i = 0; i < batch_size; i += SIMD_WIDTH) do
result[i] = col_a[i] > 100 AND col_b[i] < 200; -- 一次处理多个元素