• 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小

  • 相关阅读:
    map & reduce
    Generator
    切片
    函数参数
    Dict & Set
    list,tuple
    selenium鼠标和键盘操作
    selenium元素定位以及点击事件
    css定位
    xpath
  • 原文地址:https://www.cnblogs.com/fxjwind/p/15248474.html
Copyright © 2020-2023  润新知