Calcite分析 -- Cost

Calcite Version:1.26.0

 

初始的cost

 

加入RelNode的时候,需要更新subset的cost

 

RelSubset

找出traits想匹配的subsets,

然后逐一更新cost,

 

propagateCostImprovements0

 

getCost

叠加所有input的cost,所以input的cost是inf,按叠加后也是inf

这里注意,Convention为None的时候,cost就是inf,所以只有当RelNode转成Enumerable后,才有cost

所以只有TopDown到叶子节点,才会生成并更新subset的bestcost,因为没有input的cost是inf了

  

nonCumulativeCostHandler这里是codegen产生的,

 

RelMetadataProvider

RelOptCluster-> setMetadataProvider(DefaultRelMetadataProvider.INSTANCE);

 

 这步就是在初始化的时候将JaninoRelMetadataProvider的provider,设置成DefaultRelMetadataProvider.INSTANCE

 

DefaultRelMetadataProvider

 

其中,RelMdPercentageOriginalRows

 

这里包含了对于getNonCumulativeCost的实现,

 

下面看下如果Codegen的,

前面的revise,调用到create,

创建Key,包含Def和provider

Def,获取哪个method的handle

provide,包含所有method和实现类的对应关系

 

Handles是个cache,

 

cache中没有,调用load3创建,

前面生成handle的name,和相应的provider

 

最终通过,字符串拼接,codegen出来的代码主要是,

搞了这么多,其实就是为了为不同的RelNode生成不同的处理函数,避免手写switch case。

private org.apache.calcite.plan.RelOptCost getNonCumulativeCost_(
      org.apache.calcite.rel.RelNode r,
      org.apache.calcite.rel.metadata.RelMetadataQuery mq) {
    switch (relClasses.indexOf(r.getClass())) {
    default:
      return provider0.getNonCumulativeCost((org.apache.calcite.rel.RelNode) r, mq);
    case 3:
      return getNonCumulativeCost(((org.apache.calcite.plan.hep.HepRelVertex) r).getCurrentRel(), mq);
    case -1:
      throw new org.apache.calcite.rel.metadata.JaninoRelMetadataProvider$NoHandler(r.getClass());
    }
  }

看下这个的codegen,更明显一些,为不同的重载函数,

  private java.lang.Double getRowCount_(
      org.apache.calcite.rel.RelNode r,
      org.apache.calcite.rel.metadata.RelMetadataQuery mq) {
    switch (relClasses.indexOf(r.getClass())) {
    default:
      return provider0.getRowCount((org.apache.calcite.rel.RelNode) r, mq);
    case 2:
      return provider0.getRowCount((org.apache.calcite.plan.volcano.RelSubset) r, mq);
    case 3:
      return getRowCount(((org.apache.calcite.plan.hep.HepRelVertex) r).getCurrentRel(), mq);
    case 4:
    case 5:
    case 9:
    case 17:
    case 21:
    case 22:
    case 23:
    case 29:
    case 33:
    case 41:
    case 45:
    case 46:
    case 47:
      return provider0.getRowCount((org.apache.calcite.rel.SingleRel) r, mq);
    case 6:
    case 24:
    case 30:
      return provider0.getRowCount((org.apache.calcite.rel.core.Aggregate) r, mq);
    case 7:
    case 31:
      return provider0.getRowCount((org.apache.calcite.rel.core.Calc) r, mq);
    case 8:
    case 32:
      return provider0.getRowCount((org.apache.calcite.rel.core.Correlate) r, mq);
    case 10:
    case 25:
    case 34:
      return provider0.getRowCount((org.apache.calcite.rel.core.Filter) r, mq);
    case 11:
    case 35:
      return provider0.getRowCount((org.apache.calcite.rel.core.Intersect) r, mq);
    case 12:
    case 27:
    case 36:
    case 48:
      return provider0.getRowCount((org.apache.calcite.rel.core.Join) r, mq);
    case 13:
    case 37:
      return provider0.getRowCount((org.apache.calcite.rel.core.Minus) r, mq);
    case 14:
    case 26:
    case 38:
      return provider0.getRowCount((org.apache.calcite.rel.core.Project) r, mq);
    case 15:
    case 39:
      return provider0.getRowCount((org.apache.calcite.rel.core.Sort) r, mq);
    case 18:
    case 28:
    case 42:
      return provider0.getRowCount((org.apache.calcite.rel.core.TableScan) r, mq);
    case 19:
    case 43:
      return provider0.getRowCount((org.apache.calcite.rel.core.Union) r, mq);
    case 20:
    case 44:
      return provider0.getRowCount((org.apache.calcite.rel.core.Values) r, mq);
    case -1:
      throw new org.apache.calcite.rel.metadata.JaninoRelMetadataProvider$NoHandler(r.getClass());
    }
  }

 

computeSelfCost

getNonCumulativeCost最终还是调用到computeSelfCost

所以cost,取决于不同的RelNode的computeSelfCost实现

这里注意,AbstractConverter的cost也是inf

 

比如对于Filter,computeSelfCost

 

getRowCount又是要codegen,调用到RelMDRowCount,

  

propagateCostImprovements0

cost传播的过程,

rel#31:EnumerableTableScan.ENUMERABLE.[](table=[CATALOG, SALES, EMP])的subset是,rel#32:RelSubset#0.ENUMERABLE.[]

getParents的逻辑,是找parents里面以rel#32为input的,如果不是以rel#32为input,也没有理由propagate

对于每个parent,取所在RelSet的所有subsets,和parent的traits比较,相符合的放入队列

 

对于parent,不光需要propagate他的subset,同一个set中traits符合的都需要propagate

这个理论上也不会有一样的subset,所以看着是废话,

但是这里satisfies的逻辑,不是完全一样,只需要startwith就行,

 

所以对于,rel#29:RelSubset#0.ENUMERABLE.[3 DESC-nulls-last],rel#32:RelSubset#0.ENUMERABLE.[]也是satisfies的

所以这里结果是,有点出乎意料,

 

 当前,rel#32:RelSubset#0.ENUMERABLE.[]完成propagate,继续

 

完成rel#29和rel#25的propagate,

注意上面33,34虽然也对32创建了propagate,但是都没有31cost低,所以32的best还是31

 

 继续propagate,

 

直到root,完成propagate,

 

注意这里的Root是Subset,应该说经过一遍Register后,Plan上说有的节点都变成SubSet,为何要将RelNode变成SubSet?

因为在优化过程中,随着cost的变化,SubSet虽然不变,但是作为best的RelNode的是可以变的

为什么不用RelSet作为Root?RelSet只是个理论上的概念,现实中的数据都是带物理属性的,比如convention

这里有个设计比较麻烦?

找Subset所关联的RelNode是很麻烦的,要到Set.Rels里面去找和subset的traits匹配的所有RelNode

RelSet中所包含的Relnode,虽然在逻辑上是等价的,但是在物理上确实属于不同的subset

在物理属性上,Root的物理属性是最关键的,因为这个是最终输出给用户的,底下的所有的物理属性都是由这个决定的

所以在subset的state如果是Required,是个很强的约束,无论其他的subset是怎么样的traits,但最终还是要通过converter转换成满足required

所以这里的cost的propagate,就是沿着bottomup,一路更新cost和并试图去更新subset的best

这也是cost model,通过rule增加不同算子的目的,所以在addRelToSet后,都会试图去更新cost。

 

prune

subset.upperBound,表示这个group的cost的上限,初始化的时候是inf

当前产生bestcost的时候,会将bestcost赋值给upperBound,当有一个plan产生这样的cost,那么大于这个cost的plan都可以ignore了

onProduce也会被更新,

 

startOptimize时,也会调整upperBound的值,

 

OptimizeInput1

这里的mExpr表示待优化的算子,group表示mExpr属于的真正被优化的subset

这个mExpr是否有优化的价值,取决于当前subset.upperBound - cost(mExpr) 是否大于0

意思是如果mExpr本身的cost都已经大于subset.upperBound,那么该mExpr的input就没有继续优化的价值了

下面的例子中,

group.upperBound是rel#31:EnumerableTableScan.ENUMERABLE.[](table=[CATALOG, SALES, EMP])的bestcost

这里mExpr,rel#33:EnumerableSort.ENUMERABLE.[3](input=RelSubset#32,sort0=$3,dir0=ASC)本身的cost已经大于这个upperBound,所以没有比较继续OptimizeInput了

对于OptimizeInputs,prune的逻辑复杂些,

 

OptimizeGroup

首先,OptimizeGroup的参数upperBound,

在root首次调用OptimizeGroup的时候,传入的是inf

在OptimizeInput调用OptimizeGroup的时候,传入的是upperForInput,合乎情理

 

 两种情况下,都会终止继续优化该group,一种是该group已经完成优化了;一种是在优化中,但是传入的upperForInput比现有的group.upperBound小

 

posted on 2021-09-09 19:17  fxjwind  阅读(334)  评论(0编辑  收藏  举报