时间:2021-07-01 10:21:17 帮助过:12人阅读
1 Add(Attribute(x), Add(Literal(1), Literal(2)))
规则
可以使用规则来操作树,这些规则是从一棵树到另一棵树的函数。虽然规则可以在其输入树上运行任意代码(因为该树只是一个Scala对象),但最常见的方法是使用一组模式匹配函数来查找和替换具有特定结构的子树。 模式匹配是许多函数式语言的一个特性,它允许从代数数据类型的潜在嵌套结构中提取值。在Catalyst中,树提供了一种转换方法,该方法递归地在树的所有节点上应用模式匹配函数,将每个模式匹配转换为结果。例如,我们可以实现一个在常量之间相加???Add操作的规则,如下所示:1 tree.transform { 2 case Add(Literal(c1), Literal(c2)) => Literal(c1+c2) 3 }
将此应用于x +(1 + 2)的树会产生新的树x + 3。这里关键是使用了Scala的标准模式匹配语法,它可用于匹配对象的类型和为提取的值(这里为c1和c2)提供名称。
传递给变换的模式匹配表达式是一个部分函数,??这意味着它只需要匹配所有输入树的子集。 Catalyst将测试规则适用树的哪些部分,自动跳过并下降到不匹配的子树。这种能力意味着规则只需对给定适用优化的树进行推理,而对那些不适用的数不进行推理。因此,当新的操作符新增到系统中时,这些规则不需要修改。 规则(和一般的Scala模式匹配)可以在同一个变换调用中匹配多个模式,这使得一次实现多个转换来得非常简洁。1 tree.transform { 2 case Add(Literal(c1), Literal(c2)) => Literal(c1+c2) 3 case Add(left, Literal(0)) => left 4 case Add(Literal(0), right) => right 5 }
实际上,规则可能需要多次执行才能完全转换树。Catalyst将规则形成批处理,并执行每个批处理至固定点,该固定点是树应用其规则后不发生改变。虽然规则运行到固定点意味着每个规则是简单且自包含,但这些规则仍会对树上产生较大的全局效果。在上面的例子中,重复的应用规则会持续折叠较大的树,比如(x + 0)+(3 + 3)。另一个例子,第一个批处理可以分析所有属性指定类型的表达式,而第二批处理可使用这些类型来进行常量折叠。在每批处理完毕后,开发人员还可以对新树进行规范性检查(例如,查看所有属性为指定类型),这些检查一般使用递归匹配来编写。
最后,规则条件及其本身可以包含任意的Scala代码。这使得Catalyst比领域特定语言在优化器上更强大,同时保持简洁特性。 根据经验,对不可变树的函数转换使得整个优化器非常易于推理和调试。规则也支持在优化器中并行化,尽管该特性还没有利用这个。1 object DecimalAggregates extends Rule[LogicalPlan] { 2 /** Maximum number of decimal digits in a Long */ 3 val MAX_LONG_DIGITS = 18 4 def apply(plan: LogicalPlan): LogicalPlan = { 5 plan transformAllExpressions { 6 case Sum(e @ DecimalType.Expression(prec, scale)) 7 if prec + 10 <= MAX_LONG_DIGITS => 8 MakeDecimal(Sum(UnscaledValue(e)), prec + 10, scale) } 9 }
再举一个例子,一个12行代码的规则通过简单的正则表达式将LIKE表达式优化为String.startsWith或String.contains调用。在规则中使用任意Scala代码使得这些优化易于表达,而这些规则超越了子树结构的模式匹配。
经过统计,逻辑优化规则有800行代码。1 def compile(node: Node): AST = node match { 2 case Literal(value) => q"$value" 3 case Attribute(name) => q"row.get($name)" 4 case Add(left, right) => q"${compile(left)} + ${compile(right)}" 5 }
以q开头的字符串是quasiquotes,虽然它们看起来像字符串,但它们在编译时由Scala编译器解析,并代表其代码的AST。 Quasiquotes用$符号表示法将变量或其他AST拼接到它们中。例如,文字(1)将成为1的Scala表达式的AST,而属性(“x”)变为row.get(“x”)。最后,类似Add(Literal(1),Attribute(“x”))的树成为像1 + row.get(“x”)这样的Scala表达式的AST。
Quasiquotes在编译时进行类型检查,以确保只替换合适的AST或文字,使得它们比字符串连接更有用,并且直接生成Scala AST,而非在运行时运行Scala语法分析器。此外,它们是高度可组合的,因为每个节点的代码生成规则不需要知道其子节点返回的树是如何构建的。最后,如果Catalyst缺少表达式级别的优化,则由Scala编译器对结果代码进行进一步优化。下图显示quasiquotes生成代码其性能类似于手动优化的程序。 我们发现quasiquotes非常接近于代码生成,并且发现即使是Spark SQL的新贡献者也可以快速为新类型的表达式添加规则。 Quasiquotes也适用于在本地Java对象上运行的目标:当从这些对象访问字段时,可以直接访问所需字段,而不必将对象复制成Spark SQL 行,并使用行访问器方法。最后,将代码生成的评估与对尚未生成代码的表达式的解释评估结合起来很简单,因为编译的Scala代码可以直接使用到表达式解释器中。 Catalyst生成器总共有大约700行代码。 这篇博客文章介绍了Spark SQL的Catalyst优化器内部原理。 通过这种新颖、简单的设计使Spark社区能够快速建立原型、实现和扩展引擎。 你可以在这里阅读其余的论文。 您还可以从以下内容中找到有关Spark SQL的更多信息:深入研究Spark SQL的Catalyst优化器(原创翻译)
标签:特殊 另一个 cin 解决 into mat 常量 http 抽象