- 快召唤伙伴们来围观吧
- 微博 QQ QQ空间 贴吧
- 视频嵌入链接 文档嵌入链接
- 复制
- 微信扫一扫分享
- 已成功复制到剪贴板
Apache Doris源码阅读与解析第7讲《如何实现一个函数》
《 Apache Doris 源码阅读与解析》系列直播活动旨在帮助 Apache Doris 社区的开发者或者有意向参与 Apache Doris 社区建设的小伙伴们,可以更快熟悉 Apache Doris 代码的组织结构和一些主要流程的实现原理以及代码位置,以便于各位小伙伴们能够快速上手,参与到开发工作中来。
《第七讲 —— 如何实现一个函数》,这一讲我们将从一个具体函数入手,介绍如何为 Doris 添加一个函数:
聚合函数和标量函数
函数实现框架和函数签名
FE 查找函数的过程
展开查看详情
1 .Apache Doris 源码阅读与解析 第七讲:如何实现一个函数 李昊鹏
2 .课程介绍 • Doris的函数介绍:聚合函数与标量函数 • 函数实现框架与函数签名 • FE注册与查找函数的流程
3 .课程收获 • 了解Doris函数是如何工作的 • 从源码程度了解如何开发函数,五分钟成为Doris的Contributor
4 . 01 Doris中的函数:标量函数与聚合函数
5 .基础概念 • 标量函数:单行操作,输出单行结果 • 例如:abs,hex,bin等 • 聚合函数:多行操作,输出单行结果 • 例如:count,sum,min等 • 主要使用场景:聚合查询,开窗查询
6 .数据类型 • 函数中所有的类型包含了Doris支持的数据类型 Doris 类型 函数类型 TinyInt TinyIntVal • AnyVal是所有数据类型的父类 SmallInt SmallIntVal Int IntVal • 主要用于类型是否为null BigInt BigIntVal LargeInt LargeIntVal Float FloatVal Double DoubleVal Date DateTimeVal Datetime DateTimeVal Char StringVal Varchar StringVal Decimal DecimalVal
7 .函数上下文 • 函数中执行过程中的一些中间状态保存在函数上下文:FunctionContext • 变长参数的存储 • 函数运行时内存的分配,释放 • uint8_t* allocate(int byte_size); • uint8_t* reallocate(uint8_t* ptr, int byte_size); • void free(uint8_t* buffer); • 公共常量的存储
8 . 标量函数的定义 • [RETURNS] return_type FUNCTION name (FunciontContext*, arg_type…) • 为什么每个类型都要有函数定义?只定义一个可不可以? • 可以简化这个代码吗?
9 . 变长参数的函数定义 • [RETURNS] return_type FUNCTION name (FunciontContext*, arg_type, num_arg, arg*….) • 变长参数的函数要求最后一个变长的参数类型相同, 作为指针数组传入
10 . Prepare函数与Close函数 • void FUNCTION name (FunciontContext*,FunctionContext::FunctionStateScope) • Prepare :预备的函数的变量初始化,并存储在FunctionContext之中 • Close:Prepare初始化变量的销毁
11 . 聚合函数的定义 • void* init_fn_:初始化函数,比如sum需要初始化初始值为0,且为null • void* update_fn_:更新函数,每一行进来之后对应的数据操作,sum函数就 是加法 • void* remove_fn_: 删除函数,只用于窗口函数。当数据处理超过窗口大小时, 要对原始的聚合结果进行删除。可以理解为update_fn的逆操作 • void* merge_fn_: 归并函数,由于Doris是Mpp数据库,多个节点的进行聚合 的结果需要一次统一的归并,从而得到最终的结果。
12 .聚合函数的定义 •
13 .聚合函数的定义 • intermediateType:中间类型,进行聚合计算中间结果的存储类型,通常与 结果类型一致 • void* serialize_fn_: 序列化函数,当中间结果类型不为POD类型时,需要序列 化中间结果。以便后续merge_fn继续计算 • void* finalize_fn_: 输出函数,将最终merge或update的结果进行处理,并最 终输出的函数
14 .聚合函数的定义 • • Min函数当参数为Varchar/Char/String时的Serialize与finalize函数的实现,拷 贝了聚合中间的String,并释放了对应的内存。Pod类型则可以直接拷贝
15 .聚合函数的调用流程
16 . 02 函数实现框架与函数注册签名
17 . ScalaFnCall • ScalaFnCall 继承 Expr • 子表达式都为常量,函数为常量 • 函数的调用 • Expr的类型 • 输入的TupleRow • get_xxx_val获得计算结果
18 .Prepare
19 . Open • 计算常量值,并将结果写入 FunctionContext • 调用Prepare函数,将结果 写入FunctionContext
20 .函数调用
21 .函数的注册 • [] list: 函数的名字,可设置别名 • string:函数返回值 • [] list: 函数的参数 … 代表变长参数 • string:函数签名 • string:prepare函数签名 • string:close函数签名 • 是否向量化,Nullable的属性
22 .怎么样获取函数签名 • 函数的签名是通过C++的name mangling生成的 • 函数签名在C++中可以唯一定位到确定的函数指针 • 如何获取nm palo_be | grep “函数名”“ | grep FunctionContext • 可以利用C++filt 进行name demangling
23 .函数的Nullable属性 • Doris当中函数根据行为不同划分为4种对应的Nullable属性 • 实现的合理的Nullable属性对Doris的执行性能与内存占用有影响
24 . 03 函数在FE的注册与查找流程
25 .FunctionSet • 通过函数名的HashMap存储函数的定义 • 单个函数名可能对应多个函数,通过参数寻找最佳匹配 • UDF与UDAF与Doris的内置函数共同存储,Doris的内置函数全局生效,UDF为DB级别
26 .函数查找
27 .参数匹配原则 • 一个函数的可能存在多个不同参数的实现,如何匹配最合适的函数? • 寻找最佳匹配参数,例如hex函数有两个定义:hex(bigint), hex(varchar). • 输入参数为bigint 则匹配 hex(bigint), 入参为varchar则匹配为hex(varchar) • 如果没有匹配到合适的参数,Doris会用CompareMode尝试进行近似替换。 • 输入参数为tinyint,会匹配hex(bigint),生成表达式hex(cast (tinyint as bigint))
28 . CompareMode • IS_IDENTICAL:最严格的匹配。要求名字, 参数数目类型,可变参数完全一致 • IS_INDISTINGUISHABLE:参数数目可以用可变参数 替换,null可以替换对应类型 Fn(int, int, int) -> Fn(…) / Fn(null, int) -> Fn(int,int) • IS_SUPERTYPE_OF:在2匹配原则的继承上增加Cast 的逻辑,可以保证精度不丢失。Fn(int)-> Fn(bigint) • IS_NONSTRICT_SUPERTYPE_OF:在3匹配原则的继承上 增加Cast的逻辑,但不保证精度不丢失。Fn(int) -> Fn(tinyint)
29 .04 总结