DuckDB核心模块揭秘 | 第1期 | 向量化执行引擎之Pipeline

news2024/12/22 23:07:30

DuckDB核心模块揭秘 | 第1期 | 向量化执行引擎之Pipeline

DuckDB是一款非常火的OLAP嵌入式数据库,性能超级棒。它分为多个组件:解析器、逻辑规划器、优化器、物理规划器、执行器以及事务和存储管理层。其中解析器原语PgSQL的解析器;逻辑规划器包含binder、plan generator,前者解析所有引用的schema中的对象的表达式,将其与列名和类型匹配,后者将binder生成的AST转换成由基本逻辑查询运算符组成的树;优化器产生优化的查询计划;物理规划器将优化的查询计划转换成物理执行计划,即PhysicalOperator树。它的高性能主要得益于它的push-based pipeline向量化执行引擎。本文介绍下它的向量化引擎pipeline生成原理。

1、物理执行计划长什么样?有哪些算子?

physical_plan_generator.cpp中CreatePlan函数将逻辑计划节点转换成物理计划节点,即PhysicalOperator。有哪些算子类型呢?PhysicalOperatorType:

//===--------------------------------------------------------------------===//
// Physical Operator Types
//===--------------------------------------------------------------------===//
enum class PhysicalOperatorType : uint8_t {
  INVALID,
  ORDER_BY,
  LIMIT,
  STREAMING_LIMIT,
  LIMIT_PERCENT,
  TOP_N,
  WINDOW,
  UNNEST,
  UNGROUPED_AGGREGATE,
  HASH_GROUP_BY,
  PERFECT_HASH_GROUP_BY,
  FILTER,
  PROJECTION,
  COPY_TO_FILE,
  BATCH_COPY_TO_FILE,
  FIXED_BATCH_COPY_TO_FILE,
  RESERVOIR_SAMPLE,
  STREAMING_SAMPLE,
  STREAMING_WINDOW,
  PIVOT,


  // -----------------------------
  // Scans
  // -----------------------------
  TABLE_SCAN,
  DUMMY_SCAN,
  COLUMN_DATA_SCAN,
  CHUNK_SCAN,
  RECURSIVE_CTE_SCAN,
  CTE_SCAN,
  DELIM_SCAN,
  EXPRESSION_SCAN,
  POSITIONAL_SCAN,
  // -----------------------------
  // Joins
  // -----------------------------
  BLOCKWISE_NL_JOIN,
  NESTED_LOOP_JOIN,
  HASH_JOIN,
  CROSS_PRODUCT,
  PIECEWISE_MERGE_JOIN,
  IE_JOIN,
  DELIM_JOIN,
  INDEX_JOIN,
  POSITIONAL_JOIN,
  ASOF_JOIN,
  // -----------------------------
  // SetOps
  // -----------------------------
  UNION,
  RECURSIVE_CTE,
  CTE,


  // -----------------------------
  // Updates
  // -----------------------------
  INSERT,
  BATCH_INSERT,
  DELETE_OPERATOR,
  UPDATE,


  // -----------------------------
  // Schema
  // -----------------------------
  CREATE_TABLE,
  CREATE_TABLE_AS,
  BATCH_CREATE_TABLE_AS,
  CREATE_INDEX,
  ALTER,
  CREATE_SEQUENCE,
  CREATE_VIEW,
  CREATE_SCHEMA,
  CREATE_MACRO,
  DROP,
  PRAGMA,
  TRANSACTION,
  CREATE_TYPE,
  ATTACH,
  DETACH,


  // -----------------------------
  // Helpers
  // -----------------------------
  EXPLAIN,
  EXPLAIN_ANALYZE,
  EMPTY_RESULT,
  EXECUTE,
  PREPARE,
  VACUUM,
  EXPORT,
  SET,
  LOAD,
  INOUT_FUNCTION,
  RESULT_COLLECTOR,
  RESET,
  EXTENSION
};

让我们看一个简单inner join的例子:物理执行计划最上头是投影算子PROJECTION,然后其左子树是HASH_JOIN算子,HASH_JOIN两个子算子分别为两个顺序扫描SEQ_SCAN:

3ea7f7628e6e5e4e0f3df6c00126b2e6.png

基于物理执行计划构建出pipeline,真正执行的是pipeline。

2、物理执行计划如何构建pipeline?

2.1什么是MetaPipeline

MetaPipeline 表示一组都具有相同Sink的Pipeline。Source为输入,Sink为输出,Other Node就是其他节点,将一个物理执行计划树转换成多个pipeline。一个pipeline包含一个source和一个sink以及若干个operators。

84833e61c832082728dda14890c44fc0.png

pipeline还存在一定的依赖关系,hashjoin节点必须依赖build端的pipeline产生的数据才行,所以就需要MetaPipeline构建多个pipeline依赖关系,最后执行时仅关注pipeline就可以。

以1中的例子介绍pipeline的构建过程:

2.2 Pipeline的构建

1)最开始由Executor::InitializeInteral函数创建一个MetaPipeline。该MetaPipeline的sink为NULL,vector<>pipelines容器创建一个pipeline,该pipeline的sink为NULL。

e5db544b7cb8f8eaef1578a05cb8ec5e.png

2)接着调用root_pipeline->Build(*physical_plan)使用上面的MetaPipeline继续构建pipeline

3)physical_plan为RESULT_COLLECTOR,Build会调用对应operator的Buildipelines,即调用PhysicalResultCollector::BuildPipelines,PhysicalResultCollector为PhysicalOperator的子类。

eadf9f7f0ae268e41aac7d3419b087ec.png将当前operator即PhysicalResultCollector作为当前pipeline的source,如上图所示。

4)接着在调用CreateChildMetaPipeline创建一个child_meta_pipeline,sink节点为当前节点,即PhysicalResultCollector:并构建出和上一个pipeline的父子关系

82aa59f3f07c90a2d11a035e6f1b8bc7.png

代码:

276b061fe5a35b35756c497332e1d61d.png

5)紧接着使用child_meta_pipeline继续构建pipeline。下一个算子是PROJECTION:PhysicalProjection,它没有重写基类的BuildPipelines,那么就调用PhysicalOperator的BuildPipelines:

bb0522c43d51f4f9d259d43e902f92ba.png

projection不是sink,并且它的子节点不为空,所以在当前pipeline添加一个算子即PhysicalProjection:也就是将PhysicalProjection放到当前pipeline的operators容器中

75ee61571c5c442e649bcde293c17eda.png

6)children[0]->BuildPipelines构建当前算子PhysicalProjection子节点的pipeline。此时到了HashJoin,即需要调用PhysicalHashJoin的PhysicalJoin::BuildJoinPipelines继续构建pipeline

b9735f07c55d906028f7f706acd68cda.png

首先将HashJoin添加到当前pipeline的operator容器中(因为作为探测端的pipeline);然后保留一份当前MetaPipeline中的所有pipeline到pipelines_so_for后面使用;接着构建build端的MetaPipeline:CreateChildMetaPipeline函数完成:主要是构建一个pipeline,sink为当前PhysicalHashJoin,source为PhysicalTableScan:此时构建的pipeline如下图所示:

87ab992dd45e3aaa77c576818483cd07.png

7)然后调用op.children[0].BuildPipelines继续build探测端的pipeline,实际上将左表的PhysicalTableScan设置到探测端pipeline的Source中。如上图所示。

8)外连接需要使用步骤6)保存的pipeline,构建一个childpipeline:

64ed7562738f43d84368a3b15581c59c.png

即使用Metapipeline2的pipeline再构建一个childpipeline,需要将PhysicalProjection操作符算子也加进去,此时结构如下图所示:

2fef8a32a8ac635c7b0a160b49686ead.png

9)接着会添加依赖,都是在CreateChildPipeline函数中完成。对于当前的metapipeline,即MetaPipeline2它有两个pipeline:pipeline[0]:probe端;pipeline[1]:child pipeline。首先将当前pipeline(pipeline[0])放到dependencies[child_pipeline]中;然后调用AddDependenciesFrom(child_pipeline, last_pipeline, false)继续添加依赖关系,从last_pipeline开始继续向dependencies中添加。

例如,当前metapipeline中有n个pipeline,下面pipeline[1]为起使pipeline,pipeline[m]为dependant,那么会将中间所有的pipeline都添加到dependant依赖数组里面。

pipelines[0] 
....
pipelines[s]   ---> start
.....
pipelines[m]   ---> dependant


pipelines[n-1]

结构:unordered_map<Pipeline *, vector<Pipeline *>> dependencies;完成依赖后:

pipelines[m] : [pipelines[s]......pipelines[m-1]]

由于这里的s=0;m=1所以依赖关系为:pipelines[1] : [ pipelines[0] ],其中pipeline[1]就是child_pipeline。如此:child_pipeline : [probe pipeline],表示probe pipeline依赖child_pipeline.

10)返回到1),此时进入root_pipeline->Ready()

7439ee06274ba010902761e54ae319c9.png

以8)的metapipeline2中的pipeline[0]为例,反转前:

d5b48cddfef5e6436cc212aca187f73c.png

反转后:

cf7d86f05d857d4d266140c0512f9abf.png

11)总结:8)中为所有Metapipeline和pipeline:

第一个Metapipeline:

{pipelines[1], children[1]}

第二个Metapipeline:Children Metapipeline:

{pipelines[2], children[1]}

第三个Metapipeline:children metapipeline:

{pipelines[1], children[0]}

注意:表示的是数组大小

12)最后再次回到1)Executor::InitializeInternal函数,会从root_pipeline(他是metapipeline),递归调用所有的metapipeline的pipelines数组,将pipeline汇总到root_pipelines中:

root_pipeline->GetPipelines(root_pipelines, false);
//vector<shared_ptr<Pipeline>> root_pipelines;

这就是pipeline的一个生成过程,下期介绍这些pipeline是如何调度的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1953711.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

skynet热更新之inject

游戏服务器的热更新是一种常见的需求&#xff0c;skynet可以通过inject的方式&#xff0c;来修改一个服务的消息处理函数&#xff0c;达到热更新的效果。 skynet内置服务debug_console skynet自带了一个调试控制台服务。inject注入代码需要先启动这个服务。 skynet.newservi…

linux自动化构建工具--make/makefile

目录 1.make/makefile介绍 1.1基本认识 1.2依赖关系、依赖方法 1.3具体操作步骤 1.4进一步理解 1.5默认设置 1.6make二次使用的解释 1.7两个文件的时间问题 1.8总是被执行 1.9特殊符号介绍 1.make/makefile介绍 1.1基本认识 make是一个指令&#xff0c;makefile是一…

Dify中语音和文字间转换问题的一种暂时注释方式

本文主要解释了Dify中语音和文字间转换可能会遇到的问题&#xff0c;并给出了一种暂时注释的解决方案。 一.文本转语音可能问题 本地部署文本转语音时&#xff0c;如果遇到如下问题&#xff0c;安装ffmpeg即可。但是如果安装后&#xff0c;重启系统还是遇到这个问题该如何办&…

02 Golang面向对象编程_20240727 课程笔记

视频课程 最近发现越来越多的公司在用Golang了&#xff0c;所以精心整理了一套视频教程给大家&#xff0c;这个是其中的第二部&#xff0c;后续还会有很多。 视频已经录制完成&#xff0c;完整目录截图如下&#xff1a; 课程目录 01 结构体的声明.mp402 使用var根据结构体…

Firefox扩展程序和Java程序通信

实现Firefox扩展程序&#xff0c;和Java RMI Client端进行通信。 在Firefox工具栏注册按钮&#xff0c;点击按钮后弹出Popup.html页面&#xff0c;引用Popup.js脚本&#xff0c;通过脚本向Java RMI client发送消息&#xff0c;Java RMI Client接收消息后转发到Java RMI Server…

Docker————数据卷容器,容器互联,镜像创建

1、Docker的数据管理 管理Docker容器中的数据&#xff0c;主要有两种方式&#xff1a;数据卷&#xff08;Data Volumes&#xff09;和数据卷容器&#xff08;DataVolumes Containers&#xff09;. docker run [-i -t] [--name 容器名] 镜像名&#xff1a;标签 [容器启动命令]…

RK3568 Linux 平台开发系列讲解(内核入门篇):从内核的角度看外设芯片的驱动

在嵌入式 Linux 开发中,外设芯片的驱动是实现操作系统与硬件之间交互的关键环节。对于 RK3568 这样的处理器平台,理解如何从内核的角度构建和管理外设芯片的驱动程序至关重要。 1. 外设驱动的基础概念 外设驱动(Device Driver)是操作系统与硬件设备之间的桥梁。它负责控…

智能浇花机器人·设计说明

智能浇花机器人 目录&#xff1a; 第一章 :项目描述 1 1.1 项目简介 1 1.1.1 服务范围&#xff1a; 1 1.1.2 所处行业&#xff1a; 1 1.2 项目背景 1 1.3 创新点与项目特色 3 第二章 :设计说明书 4 2.1 主要构成&#xff1a; 4 2.1.1 循迹小车 4 2.1.2 机械…

网络编程——wireshark抓包、tcp粘包

目录 一、前言 1.1 什么是粘包 1.2 为什么UDP不会粘包 二、编写程序 文件树 客户端程序 服务器程序 tcp程序 头文件 makefile 三、 实验现象 四、改进实验 五、小作业 一、前言 最近在做网络芯片的驱动&#xff0c;验证功能的时候需要借助wireshark这个工具&…

DataX(二):DataX安装与入门

1. 官方地址 下载地址&#xff1a;http://datax-opensource.oss-cn-hangzhou.aliyuncs.com/datax.tar.gz 源码地址&#xff1a;GitHub - alibaba/DataX: DataX是阿里云DataWorks数据集成的开源版本。 2. 前置要求 Linux JDK(1.8 以上&#xff0c;推荐 1.8) Python(推荐 Pyt…

C语言内存函数精讲

目录 引言 1.内存分配函数malloc 2.内存释放函数free 3.内存拷贝函数memcpy 4.内存移动函数memmove 5.内存设置函数memset 6.内存比较函数memcmp 总结 引言 在C语言编程中&#xff0c;内存管理是核心技能之一。C语言提供了一系列内存操作函数&#xff0c;这些函数在动…

机器学习 第7章-贝叶斯分类器

机器学习 第7章-贝叶斯分类器 7.1 贝叶斯决策论 贝叶斯决策论(Bayesian decision theory)是概率框架下实施决策的基本方法。对分类任务来说&#xff0c;在所有相关概率都已知的理想情形下&#xff0c;贝叶斯决策论考虑如何基于这些概率和误判损失来选择最优的类别标记。下面我…

Linux Vim全能攻略:实战代码,轻松掌握文本编辑神器

1. Vim简介与安装 1.1 Vim的历史与发展 Vim&#xff08;Vi IMproved&#xff09;是一款高度可配置的文本编辑器&#xff0c;它起源于1976年由Bill Joy开发的Vi编辑器。Vi是Unix系统上最古老的文本编辑器之一&#xff0c;因其强大的功能和高效的编辑方式而广受欢迎。随着时间的…

流媒体服务器一:搭建RTMP流媒体服务器搭建

1 安装和测试srs流媒体服务器 服务器&#xff1a;SRS(Simple RTMP Server&#xff0c;⽀持RTMP、HTTP-FLV&#xff0c;HLS) 推流端&#xff1a;ffmpeg OBS 拉流端&#xff1a;ffplay VLC srs播放器 1.1 安装srs流媒体服务器 官网 SRS (Simple Realtime Server) | SRS 码…

【一图流】Git下载与安装教程

下载Git Git官网&#xff1a;https://git-scm.com/?hlzh-cn 安装Git

全栈嵌入式C++、STM32、Modbus、FreeRTOS和MQTT协议:工业物联网(IIoT)可视化系统设计思路(附部分代码解析)

项目概述 随着工业4.0时代的到来&#xff0c;工业物联网&#xff08;IIoT&#xff09;在提高生产效率、降低运营成本和实现智能制造方面得到了广泛应用。本项目旨在开发一个全面的工业物联网监控系统&#xff0c;能够实时监测设备的温度、压力、振动和电流等参数&#xff0c;并…

浅析Jeecgboot中mybatisplus不支持Postgres SKIP LOCKED语法问题

目录 1、场景及问题 2、数据库及各框架版本信息 3、错误回放 4、根因分析及确认 5、解决问题 6、总结 1、场景及问题 场景&#xff1a; 在调用腾讯位置服务时有用到key值&#xff0c;因为每个key值都有自己的额度&#xff0c;所以在表里存了多个key&#xff0c;简称key池&…

基于Java的城市公交管理系统/SSM的城市公交查询系统/计算机专业/课设

摘 要 网络技术的不断发展&#xff0c;使网络成为人们的日常生活中不可缺少的一部分&#xff0c;而城市公交管理系统是网络的一种新型体现&#xff0c;它以其特有的便捷和快速的特点得到了广泛的认可。当前的城市公交管理系统不仅没有建立起整体的管理系统&#xff0c;为企业定…

Go语言中常见的多线程同步方法

什么是线程、进程、协程 Go 源文件经过编译器处理后&#xff0c;会产生可执行文件&#xff0c;不同系统有不同的格式。可执行文件在操作系统上执行一次&#xff0c;就对应一个进程 进程可以理解为执行中的程序&#xff0c;是一个动态的概念&#xff0c;同一份可执行文件执行多…

Django 表单error_messages , 表单校验提示

在Django中&#xff0c;error_messages是表单字段的一个参数&#xff0c;允许你为特定的验证错误自定义错误消息。默认情况下&#xff0c;Django的表单字段会为常见的验证错误提供默认的错误消息。但是&#xff0c;你可能想要为你的应用提供更加用户友好的或者本地化的错误消息…