CostV2.java

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package org.apache.doris.nereids.cost;

import org.apache.doris.qe.ConnectContext;

/**
 * This cost model calculates the distributed cost by dividing it into two components: startCost and runCost.
 * The startCost represents the cost of an operator starting to emit the first tuple, while the runCost represents
 * the cost of the operator emitting all tuples.
 * <p>
 * If all operators run in parallel, the child and parent operators can be represented as follows:
 * childStart ---> childRun
 * |---> operatorStart ---> operatorRun
 * <p>
 * If all operators run serially, the order would be:
 * childStart ---> childRun ---> operatorStart ---> operatorRun
 * <p>
 * The degree of parallelism is controlled by the decay parameter, with a value of 1 indicating perfect serial execution
 * and a value of 0 indicating perfect parallel execution.
 */
class CostV2 implements Cost {
    double memory;
    double runCost;
    double startCost;
    double childStartCost;
    double childRunCost;
    double cost;

    double leftStartCost = 0;
    double limitRatio = 1;

    /**
     * Constructor of CostV2.
     */
    CostV2(double startCost, double runCost, double memory) {
        this.memory = memory;
        this.runCost = makeValidDouble(runCost);
        this.startCost = makeValidDouble(startCost);
        this.childRunCost = 0;
        this.childStartCost = 0;
        this.cost = this.startCost + this.runCost;
    }

    public void setLimitRation(double ratio) {
        this.limitRatio = Double.min(1, ratio);
    }

    public double getLimitRation() {
        return limitRatio;
    }

    public void updateChildCost(double childStartCost, double childRunCost, double memory) {
        childStartCost = makeValidDouble(childStartCost);
        childRunCost = makeValidDouble(childRunCost);
        this.childStartCost = Double.max(childStartCost, this.childStartCost);
        this.childRunCost = Double.max(childRunCost, this.childRunCost);
        this.cost = startCost + this.childStartCost + Double.max(
                this.childRunCost + this.runCost * CostWeight.getDelay(), this.runCost);
        this.memory += memory;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(runCost)
                .append("/").append(startCost)
                .append("/").append(cost);
        return sb.toString();
    }

    private double makeValidDouble(Double value) {
        if (Double.isNaN(value)) {
            return 0;
        }
        if (Double.isInfinite(value)) {
            return Double.MAX_VALUE / 1000;
        }
        return value;
    }

    @Override
    public double getValue() {
        double maxExecMemByte = ConnectContext.get().getSessionVariable().getMaxExecMemByte();
        if (memory > maxExecMemByte) {
            cost *= Math.ceil(memory / maxExecMemByte);
        }
        return cost;
    }

    public void finish() {
        startCost = startCost + childStartCost;
        runCost = cost - startCost;
    }

    public double getRunCost() {
        return runCost;
    }

    public double getStartCost() {
        return startCost;
    }

    public double getCost() {
        return cost;
    }

    public double getMemory() {
        return memory;
    }

    public static Cost zero() {
        return new CostV2(0, 0, 0);
    }

    public static Cost infinite() {
        return new CostV2(0, Double.MAX_VALUE, Double.MAX_VALUE);
    }
}