栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spark 之 WholeStageCodegen

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spark 之 WholeStageCodegen

CodeGen framework
  • CodegenSupport(接口)
    相邻Operator通过Produce-Consume模式生成代码。
    Produce生成整体处理的框架代码,例如aggregation生成的代码框架如下:
if (!initialized) {
 # create a hash map, then build the aggregation hash map
 # call child.produce()
 initialized = true;
}
while (hashmap.hasNext()) {
 row = hashmap.next();
 # build the aggregation results
 # create variables for results
 # call consume(), which will call parent.doConsume()
 if (shouldStop()) return;
}

Consume生成当前节点处理上游输入的Row的逻辑。如Filter生成代码如下:

# code to evaluate the predicate expression, result is isNull1 and value2
if (!isNull1 && value2) {
 # call consume(), which will call parent.doConsume()
}
  • WholeStageCodegenExec(类)
    CodegenSupport的实现类之一,Stage内部所有相邻的实现CodegenSupport接口的Operator的融合,产出的代码把所有被融合的Operator的执行逻辑封装到一个Wrapper类中,该Wrapper类作为Janino即时compile的入参。
  • InputAdapter(类)
    CodegenSupport的实现类之一,胶水类,用来连接WholeStageCodegenExec节点和未实现CodegenSupport的上游节点。
  • BufferedRowIterator(接口)
    WholeStageCodegenExec生成的java代码的父类,重要方法:
public InternalRow next() // 返回下一条Row
public void append(InternalRow row) // append一条Row
Simple call graph
 
Produce-Consume Pattern 

doProduce() doConsume() 会被子类覆写
produce() consume() 均为 trait CodegenSupport extends SparkPlan 的 final 方法

insertInputAdapter InputAdapter(类)

CodegenSupport的实现类之一,胶水类,用来连接WholeStageCodegenExec节点和未实现CodegenSupport的上游节点。

  private def insertInputAdapter(plan: SparkPlan): SparkPlan = {
    plan match {
      case p if !supportCodegen(p) =>
        // collapse them recursively
        InputAdapter(insertWholeStageCodegen(p))
      case j: SortMergeJoinExec =>
        // The children of SortMergeJoin should do codegen separately.
        j.withNewChildren(j.children.map(
          child => InputAdapter(insertWholeStageCodegen(child))))
      case j: ShuffledHashJoinExec =>
        // The children of ShuffledHashJoin should do codegen separately.
        j.withNewChildren(j.children.map(
          child => InputAdapter(insertWholeStageCodegen(child))))
      case p => p.withNewChildren(p.children.map(insertInputAdapter))
    }
  }
UT
test("range/filter should be combined") {
    val df = spark.range(10).filter("id = 1").selectExpr("id + 1")
    val plan = df.queryExecution.executedPlan
    assert(plan.find(_.isInstanceOf[WholeStageCodegenExec]).isDefined)
    assert(df.collect() === Array(Row(2)))
    df.explain(false)
    df.queryExecution.debug.codegen
  }
11:32:34.837 WARN org.apache.hadoop.util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
== Physical Plan ==
*(1) Project [(id#0L + 1) AS (id + 1)#4L]
+- *(1) Filter (id#0L = 1)
   +- *(1) Range (0, 10, step=1, splits=2)


Found 1 WholeStageCodegen subtrees.
== Subtree 1 / 1 (maxMethodCodeSize:301; maxConstantPoolSize:177(0.27% used); numInnerClasses:0) ==
*(1) Project [(id#0L + 1) AS (id + 1)#4L]
+- *(1) Filter (id#0L = 1)
   +- *(1) Range (0, 10, step=1, splits=2)

Generated code:
 public Object generate(Object[] references) {
   return new GeneratedIteratorForCodegenStage1(references);
 }

 // codegenStageId=1
 final class GeneratedIteratorForCodegenStage1 extends org.apache.spark.sql.execution.BufferedRowIterator {
   private Object[] references;
   private scala.collection.Iterator[] inputs;
   private boolean range_initRange_0;
   private long range_nextIndex_0;
   private TaskContext range_taskContext_0;
   private InputMetrics range_inputMetrics_0;
   private long range_batchEnd_0;
   private long range_numElementsTodo_0;
   private org.apache.spark.sql.catalyst.expressions.codegen.UnsafeRowWriter[] range_mutableStateArray_0 = new org.apache.spark.sql.catalyst.expressions.codegen.UnsafeRowWriter[3];

   public GeneratedIteratorForCodegenStage1(Object[] references) {
     this.references = references;
   }

   public void init(int index, scala.collection.Iterator[] inputs) {
     partitionIndex = index;
     this.inputs = inputs;

     range_taskContext_0 = TaskContext.get();
     range_inputMetrics_0 = range_taskContext_0.taskMetrics().inputMetrics();
     range_mutableStateArray_0[0] = new org.apache.spark.sql.catalyst.expressions.codegen.UnsafeRowWriter(1, 0);
     range_mutableStateArray_0[1] = new org.apache.spark.sql.catalyst.expressions.codegen.UnsafeRowWriter(1, 0);
     range_mutableStateArray_0[2] = new org.apache.spark.sql.catalyst.expressions.codegen.UnsafeRowWriter(1, 0);

   }

   private void initRange(int idx) {
     java.math.BigInteger index = java.math.BigInteger.valueOf(idx);
     java.math.BigInteger numSlice = java.math.BigInteger.valueOf(2L);
     java.math.BigInteger numElement = java.math.BigInteger.valueOf(10L);
     java.math.BigInteger step = java.math.BigInteger.valueOf(1L);
     java.math.BigInteger start = java.math.BigInteger.valueOf(0L);
     long partitionEnd;

     java.math.BigInteger st = index.multiply(numElement).divide(numSlice).multiply(step).add(start);
     if (st.compareTo(java.math.BigInteger.valueOf(Long.MAX_VALUE)) > 0) {
       range_nextIndex_0 = Long.MAX_VALUE;
     } else if (st.compareTo(java.math.BigInteger.valueOf(Long.MIN_VALUE)) < 0) {
       range_nextIndex_0 = Long.MIN_VALUE;
     } else {
       range_nextIndex_0 = st.longValue();
     }
     range_batchEnd_0 = range_nextIndex_0;

     java.math.BigInteger end = index.add(java.math.BigInteger.ONE).multiply(numElement).divide(numSlice)
     .multiply(step).add(start);
     if (end.compareTo(java.math.BigInteger.valueOf(Long.MAX_VALUE)) > 0) {
       partitionEnd = Long.MAX_VALUE;
     } else if (end.compareTo(java.math.BigInteger.valueOf(Long.MIN_VALUE)) < 0) {
       partitionEnd = Long.MIN_VALUE;
     } else {
       partitionEnd = end.longValue();
     }

     java.math.BigInteger startToEnd = java.math.BigInteger.valueOf(partitionEnd).subtract(
       java.math.BigInteger.valueOf(range_nextIndex_0));
     range_numElementsTodo_0  = startToEnd.divide(step).longValue();
     if (range_numElementsTodo_0 < 0) {
       range_numElementsTodo_0 = 0;
     } else if (startToEnd.remainder(step).compareTo(java.math.BigInteger.valueOf(0L)) != 0) {
       range_numElementsTodo_0++;
     }
   }

   protected void processNext() throws java.io.IOException {
     // initialize Range
     if (!range_initRange_0) {
       range_initRange_0 = true;
       initRange(partitionIndex);
     }

     while (true) {
       if (range_nextIndex_0 == range_batchEnd_0) {
         long range_nextBatchTodo_0;
         if (range_numElementsTodo_0 > 1000L) {
           range_nextBatchTodo_0 = 1000L;
           range_numElementsTodo_0 -= 1000L;
         } else {
           range_nextBatchTodo_0 = range_numElementsTodo_0;
           range_numElementsTodo_0 = 0;
           if (range_nextBatchTodo_0 == 0) break;
         }
         range_batchEnd_0 += range_nextBatchTodo_0 * 1L;
       }

       int range_localEnd_0 = (int)((range_batchEnd_0 - range_nextIndex_0) / 1L);
       for (int range_localIdx_0 = 0; range_localIdx_0 < range_localEnd_0; range_localIdx_0++) {
         long range_value_0 = ((long)range_localIdx_0 * 1L) + range_nextIndex_0;

         do {
           boolean filter_value_0 = false;
           filter_value_0 = range_value_0 == 1L;
           if (!filter_value_0) continue;

           ((org.apache.spark.sql.execution.metric.SQLMetric) references[1] ).add(1);

           // common sub-expressions

           long project_value_0 = -1L;

           project_value_0 = range_value_0 + 1L;
           range_mutableStateArray_0[2].reset();

           range_mutableStateArray_0[2].write(0, project_value_0);
           append((range_mutableStateArray_0[2].getRow()));

         } while(false);

         if (shouldStop()) {
           range_nextIndex_0 = range_value_0 + 1L;
           ((org.apache.spark.sql.execution.metric.SQLMetric) references[0] ).add(range_localIdx_0 + 1);
           range_inputMetrics_0.incRecordsRead(range_localIdx_0 + 1);
           return;
         }

       }
       range_nextIndex_0 = range_batchEnd_0;
       ((org.apache.spark.sql.execution.metric.SQLMetric) references[0] ).add(range_localEnd_0);
       range_inputMetrics_0.incRecordsRead(range_localEnd_0);
       range_taskContext_0.killTaskIfInterrupted();
     }
   }

 }


11:32:40.126 WARN org.apache.spark.sql.execution.WholeStageCodegenSuite: 

===== POSSIBLE THREAD LEAK IN SUITE o.a.s.sql.execution.WholeStageCodegenSuite, thread names: rpc-boss-3-1, shuffle-boss-6-1 =====


Process finished with exit code 0

一个简单的实验
case class FilterExec(condition: Expression, child: SparkPlan)

在FilterExec 增加这一行

override def supportCodegen: Boolean = false

会拆成两个wholestagecodegen

16:27:42.332 WARN org.apache.hadoop.util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
== Physical Plan ==
*(2) Project [(id#0L + 1) AS (id + 1)#4L]
+- Filter (id#0L = 1)
   +- *(1) Range (0, 10, step=1, splits=2)


Found 2 WholeStageCodegen subtrees.
== Subtree 1 / 2 (maxMethodCodeSize:282; maxConstantPoolSize:175(0.27% used); numInnerClasses:0) ==
*(1) Range (0, 10, step=1, splits=2)

Generated code:
 public Object generate(Object[] references) {
   return new GeneratedIteratorForCodegenStage1(references);
 }

 // codegenStageId=1
 final class GeneratedIteratorForCodegenStage1 extends org.apache.spark.sql.execution.BufferedRowIterator {
   private Object[] references;
   private scala.collection.Iterator[] inputs;
   private boolean range_initRange_0;
   private long range_nextIndex_0;
   private TaskContext range_taskContext_0;
   private InputMetrics range_inputMetrics_0;
   private long range_batchEnd_0;
   private long range_numElementsTodo_0;
   private org.apache.spark.sql.catalyst.expressions.codegen.UnsafeRowWriter[] range_mutableStateArray_0 = new org.apache.spark.sql.catalyst.expressions.codegen.UnsafeRowWriter[1];

   public GeneratedIteratorForCodegenStage1(Object[] references) {
     this.references = references;
   }

   public void init(int index, scala.collection.Iterator[] inputs) {
     partitionIndex = index;
     this.inputs = inputs;

     range_taskContext_0 = TaskContext.get();
     range_inputMetrics_0 = range_taskContext_0.taskMetrics().inputMetrics();
     range_mutableStateArray_0[0] = new org.apache.spark.sql.catalyst.expressions.codegen.UnsafeRowWriter(1, 0);

   }

   private void initRange(int idx) {
     java.math.BigInteger index = java.math.BigInteger.valueOf(idx);
     java.math.BigInteger numSlice = java.math.BigInteger.valueOf(2L);
     java.math.BigInteger numElement = java.math.BigInteger.valueOf(10L);
     java.math.BigInteger step = java.math.BigInteger.valueOf(1L);
     java.math.BigInteger start = java.math.BigInteger.valueOf(0L);
     long partitionEnd;

     java.math.BigInteger st = index.multiply(numElement).divide(numSlice).multiply(step).add(start);
     if (st.compareTo(java.math.BigInteger.valueOf(Long.MAX_VALUE)) > 0) {
       range_nextIndex_0 = Long.MAX_VALUE;
     } else if (st.compareTo(java.math.BigInteger.valueOf(Long.MIN_VALUE)) < 0) {
       range_nextIndex_0 = Long.MIN_VALUE;
     } else {
       range_nextIndex_0 = st.longValue();
     }
     range_batchEnd_0 = range_nextIndex_0;

     java.math.BigInteger end = index.add(java.math.BigInteger.ONE).multiply(numElement).divide(numSlice)
     .multiply(step).add(start);
     if (end.compareTo(java.math.BigInteger.valueOf(Long.MAX_VALUE)) > 0) {
       partitionEnd = Long.MAX_VALUE;
     } else if (end.compareTo(java.math.BigInteger.valueOf(Long.MIN_VALUE)) < 0) {
       partitionEnd = Long.MIN_VALUE;
     } else {
       partitionEnd = end.longValue();
     }

     java.math.BigInteger startToEnd = java.math.BigInteger.valueOf(partitionEnd).subtract(
       java.math.BigInteger.valueOf(range_nextIndex_0));
     range_numElementsTodo_0  = startToEnd.divide(step).longValue();
     if (range_numElementsTodo_0 < 0) {
       range_numElementsTodo_0 = 0;
     } else if (startToEnd.remainder(step).compareTo(java.math.BigInteger.valueOf(0L)) != 0) {
       range_numElementsTodo_0++;
     }
   }

   protected void processNext() throws java.io.IOException {
     // initialize Range
     if (!range_initRange_0) {
       range_initRange_0 = true;
       initRange(partitionIndex);
     }

     while (true) {
       if (range_nextIndex_0 == range_batchEnd_0) {
         long range_nextBatchTodo_0;
         if (range_numElementsTodo_0 > 1000L) {
           range_nextBatchTodo_0 = 1000L;
           range_numElementsTodo_0 -= 1000L;
         } else {
           range_nextBatchTodo_0 = range_numElementsTodo_0;
           range_numElementsTodo_0 = 0;
           if (range_nextBatchTodo_0 == 0) break;
         }
         range_batchEnd_0 += range_nextBatchTodo_0 * 1L;
       }

       int range_localEnd_0 = (int)((range_batchEnd_0 - range_nextIndex_0) / 1L);
       for (int range_localIdx_0 = 0; range_localIdx_0 < range_localEnd_0; range_localIdx_0++) {
         long range_value_0 = ((long)range_localIdx_0 * 1L) + range_nextIndex_0;

         range_mutableStateArray_0[0].reset();

         range_mutableStateArray_0[0].write(0, range_value_0);
         append((range_mutableStateArray_0[0].getRow()));

         if (shouldStop()) {
           range_nextIndex_0 = range_value_0 + 1L;
           ((org.apache.spark.sql.execution.metric.SQLMetric) references[0] ).add(range_localIdx_0 + 1);
           range_inputMetrics_0.incRecordsRead(range_localIdx_0 + 1);
           return;
         }

       }
       range_nextIndex_0 = range_batchEnd_0;
       ((org.apache.spark.sql.execution.metric.SQLMetric) references[0] ).add(range_localEnd_0);
       range_inputMetrics_0.incRecordsRead(range_localEnd_0);
       range_taskContext_0.killTaskIfInterrupted();
     }
   }

 }

== Subtree 2 / 2 (maxMethodCodeSize:89; maxConstantPoolSize:91(0.14% used); numInnerClasses:0) ==
*(2) Project [(id#0L + 1) AS (id + 1)#4L]
+- Filter (id#0L = 1)
   +- *(1) Range (0, 10, step=1, splits=2)

Generated code:
 public Object generate(Object[] references) {
   return new GeneratedIteratorForCodegenStage2(references);
 }

 // codegenStageId=2
 final class GeneratedIteratorForCodegenStage2 extends org.apache.spark.sql.execution.BufferedRowIterator {
   private Object[] references;
   private scala.collection.Iterator[] inputs;
   private scala.collection.Iterator inputadapter_input_0;
   private org.apache.spark.sql.catalyst.expressions.codegen.UnsafeRowWriter[] project_mutableStateArray_0 = new org.apache.spark.sql.catalyst.expressions.codegen.UnsafeRowWriter[1];

   public GeneratedIteratorForCodegenStage2(Object[] references) {
     this.references = references;
   }

   public void init(int index, scala.collection.Iterator[] inputs) {
     partitionIndex = index;
     this.inputs = inputs;
     inputadapter_input_0 = inputs[0];
     project_mutableStateArray_0[0] = new org.apache.spark.sql.catalyst.expressions.codegen.UnsafeRowWriter(1, 0);

   }

   protected void processNext() throws java.io.IOException {
     while ( inputadapter_input_0.hasNext()) {
       InternalRow inputadapter_row_0 = (InternalRow) inputadapter_input_0.next();

       // common sub-expressions

       long inputadapter_value_0 = inputadapter_row_0.getLong(0);

       long project_value_0 = -1L;

       project_value_0 = inputadapter_value_0 + 1L;
       project_mutableStateArray_0[0].reset();

       project_mutableStateArray_0[0].write(0, project_value_0);
       append((project_mutableStateArray_0[0].getRow()));
       if (shouldStop()) return;
     }
   }

 }


16:27:47.464 WARN org.apache.spark.sql.execution.WholeStageCodegenSuite: 

===== POSSIBLE THREAD LEAK IN SUITE o.a.s.sql.execution.WholeStageCodegenSuite, thread names: rpc-boss-3-1, shuffle-boss-6-1 =====


Process finished with exit code 0

最里侧的operator

比如rangeExec, 肯定要实现 doproduce方法,但 consume不需要实现,直接调用父类的consume()

case class RangeExec(range: org.apache.spark.sql.catalyst.plans.logical.Range)
  extends LeafExecNode with CodegenSupport {

  val start: Long = range.start
  val end: Long = range.end
  val step: Long = range.step
  val numSlices: Int = range.numSlices.getOrElse(sparkContext.defaultParallelism)
  val numElements: BigInt = range.numElements
  val isEmptyRange: Boolean = start == end || (start < end ^ 0 < step)

  override val output: Seq[Attribute] = range.output

  override def outputOrdering: Seq[SortOrder] = range.outputOrdering

  override def outputPartitioning: Partitioning = {
    if (numElements > 0) {
      if (numSlices == 1) {
        SinglePartition
      } else {
        RangePartitioning(outputOrdering, numSlices)
      }
    } else {
      UnknownPartitioning(0)
    }
  }

  override lazy val metrics = Map(
    "numOutputRows" -> SQLMetrics.createMetric(sparkContext, "number of output rows"))

  override def doCanonicalize(): SparkPlan = {
    RangeExec(range.canonicalized.asInstanceOf[org.apache.spark.sql.catalyst.plans.logical.Range])
  }

  override def inputRDDs(): Seq[RDD[InternalRow]] = {
    val rdd = if (isEmptyRange) {
      new EmptyRDD[InternalRow](sqlContext.sparkContext)
    } else {
      sqlContext.sparkContext.parallelize(0 until numSlices, numSlices).map(i => InternalRow(i))
    }
    rdd :: Nil
  }

  protected override def doProduce(ctx: CodegenContext): String = {
    val numOutput = metricTerm(ctx, "numOutputRows")

    val initTerm = ctx.addMutableState(CodeGenerator.JAVA_BOOLEAN, "initRange")
    val nextIndex = ctx.addMutableState(CodeGenerator.JAVA_LONG, "nextIndex")

    val value = ctx.freshName("value")
    val ev = ExprCode.forNonNullValue(JavaCode.variable(value, LongType))
    val BigInt = classOf[java.math.BigInteger].getName

    // Inline mutable state since not many Range operations in a task
    val taskContext = ctx.addMutableState("TaskContext", "taskContext",
      v => s"$v = TaskContext.get();", forceInline = true)
    val inputMetrics = ctx.addMutableState("InputMetrics", "inputMetrics",
      v => s"$v = $taskContext.taskMetrics().inputMetrics();", forceInline = true)

    // In order to periodically update the metrics without inflicting performance penalty, this
    // operator produces elements in batches. After a batch is complete, the metrics are updated
    // and a new batch is started.
    // In the implementation below, the code in the inner loop is producing all the values
    // within a batch, while the code in the outer loop is setting batch parameters and updating
    // the metrics.

    // Once nextIndex == batchEnd, it's time to progress to the next batch.
    val batchEnd = ctx.addMutableState(CodeGenerator.JAVA_LONG, "batchEnd")

    // How many values should still be generated by this range operator.
    val numElementsTodo = ctx.addMutableState(CodeGenerator.JAVA_LONG, "numElementsTodo")

    // How many values should be generated in the next batch.
    val nextBatchTodo = ctx.freshName("nextBatchTodo")

    // The default size of a batch, which must be positive integer
    val batchSize = 1000

    val initRangeFuncName = ctx.addNewFunction("initRange",
      s"""
        | private void initRange(int idx) {
        |   $BigInt index = $BigInt.valueOf(idx);
        |   $BigInt numSlice = $BigInt.valueOf(${numSlices}L);
        |   $BigInt numElement = $BigInt.valueOf(${numElements.toLong}L);
        |   $BigInt step = $BigInt.valueOf(${step}L);
        |   $BigInt start = $BigInt.valueOf(${start}L);
        |   long partitionEnd;
        |
        |   $BigInt st = index.multiply(numElement).divide(numSlice).multiply(step).add(start);
        |   if (st.compareTo($BigInt.valueOf(Long.MAX_VALUE)) > 0) {
        |     $nextIndex = Long.MAX_VALUE;
        |   } else if (st.compareTo($BigInt.valueOf(Long.MIN_VALUE)) < 0) {
        |     $nextIndex = Long.MIN_VALUE;
        |   } else {
        |     $nextIndex = st.longValue();
        |   }
        |   $batchEnd = $nextIndex;
        |
        |   $BigInt end = index.add($BigInt.ONE).multiply(numElement).divide(numSlice)
        |     .multiply(step).add(start);
        |   if (end.compareTo($BigInt.valueOf(Long.MAX_VALUE)) > 0) {
        |     partitionEnd = Long.MAX_VALUE;
        |   } else if (end.compareTo($BigInt.valueOf(Long.MIN_VALUE)) < 0) {
        |     partitionEnd = Long.MIN_VALUE;
        |   } else {
        |     partitionEnd = end.longValue();
        |   }
        |
        |   $BigInt startToEnd = $BigInt.valueOf(partitionEnd).subtract(
        |     $BigInt.valueOf($nextIndex));
        |   $numElementsTodo  = startToEnd.divide(step).longValue();
        |   if ($numElementsTodo < 0) {
        |     $numElementsTodo = 0;
        |   } else if (startToEnd.remainder(step).compareTo($BigInt.valueOf(0L)) != 0) {
        |     $numElementsTodo++;
        |   }
        | }
       """.stripMargin)

    val localIdx = ctx.freshName("localIdx")
    val localEnd = ctx.freshName("localEnd")
    val stopCheck = if (parent.needStopCheck) {
      s"""
         |if (shouldStop()) {
         |  $nextIndex = $value + ${step}L;
         |  $numOutput.add($localIdx + 1);
         |  $inputMetrics.incRecordsRead($localIdx + 1);
         |  return;
         |}
       """.stripMargin
    } else {
      "// shouldStop check is eliminated"
    }
    val loopCondition = if (limitNotReachedChecks.isEmpty) {
      "true"
    } else {
      limitNotReachedChecks.mkString(" && ")
    }

    // An overview of the Range processing.
    //
    // For each partition, the Range task needs to produce records from partition start(inclusive)
    // to end(exclusive). For better performance, we separate the partition range into batches, and
    // use 2 loops to produce data. The outer while loop is used to iterate batches, and the inner
    // for loop is used to iterate records inside a batch.
    //
    // `nextIndex` tracks the index of the next record that is going to be consumed, initialized
    // with partition start. `batchEnd` tracks the end index of the current batch, initialized
    // with `nextIndex`. In the outer loop, we first check if `nextIndex == batchEnd`. If it's true,
    // it means the current batch is fully consumed, and we will update `batchEnd` to process the
    // next batch. If `batchEnd` reaches partition end, exit the outer loop. Finally we enter the
    // inner loop. Note that, when we enter inner loop, `nextIndex` must be different from
    // `batchEnd`, otherwise we already exit the outer loop.
    //
    // The inner loop iterates from 0 to `localEnd`, which is calculated by
    // `(batchEnd - nextIndex) / step`. Since `batchEnd` is increased by `nextBatchTodo * step` in
    // the outer loop, and initialized with `nextIndex`, so `batchEnd - nextIndex` is always
    // divisible by `step`. The `nextIndex` is increased by `step` during each iteration, and ends
    // up being equal to `batchEnd` when the inner loop finishes.
    //
    // The inner loop can be interrupted, if the query has produced at least one result row, so that
    // we don't buffer too many result rows and waste memory. It's ok to interrupt the inner loop,
    // because `nextIndex` will be updated before interrupting.

    s"""
      | // initialize Range
      | if (!$initTerm) {
      |   $initTerm = true;
      |   $initRangeFuncName(partitionIndex);
      | }
      |
      | while ($loopCondition) {
      |   if ($nextIndex == $batchEnd) {
      |     long $nextBatchTodo;
      |     if ($numElementsTodo > ${batchSize}L) {
      |       $nextBatchTodo = ${batchSize}L;
      |       $numElementsTodo -= ${batchSize}L;
      |     } else {
      |       $nextBatchTodo = $numElementsTodo;
      |       $numElementsTodo = 0;
      |       if ($nextBatchTodo == 0) break;
      |     }
      |     $batchEnd += $nextBatchTodo * ${step}L;
      |   }
      |
      |   int $localEnd = (int)(($batchEnd - $nextIndex) / ${step}L);
      |   for (int $localIdx = 0; $localIdx < $localEnd; $localIdx++) {
      |     long $value = ((long)$localIdx * ${step}L) + $nextIndex;
      |     ${consume(ctx, Seq(ev))}
      |     $stopCheck
      |   }
      |   $nextIndex = $batchEnd;
      |   $numOutput.add($localEnd);
      |   $inputMetrics.incRecordsRead($localEnd);
      |   $taskContext.killTaskIfInterrupted();
      | }
     """.stripMargin
  }

  protected override def doExecute(): RDD[InternalRow] = {
    val numOutputRows = longMetric("numOutputRows")
    if (isEmptyRange) {
      new EmptyRDD[InternalRow](sqlContext.sparkContext)
    } else {
      sqlContext
        .sparkContext
        .parallelize(0 until numSlices, numSlices)
        .mapPartitionsWithIndex { (i, _) =>
          val partitionStart = (i * numElements) / numSlices * step + start
          val partitionEnd = (((i + 1) * numElements) / numSlices) * step + start

          def getSafeMargin(bi: BigInt): Long =
            if (bi.isValidLong) {
              bi.toLong
            } else if (bi > 0) {
              Long.MaxValue
            } else {
              Long.MinValue
            }

          val safePartitionStart = getSafeMargin(partitionStart)
          val safePartitionEnd = getSafeMargin(partitionEnd)
          val rowSize = UnsafeRow.calculateBitSetWidthInBytes(1) + LongType.defaultSize
          val unsafeRow = UnsafeRow.createFromByteArray(rowSize, 1)
          val taskContext = TaskContext.get()

          val iter = new Iterator[InternalRow] {
            private[this] var number: Long = safePartitionStart
            private[this] var overflow: Boolean = false
            private[this] val inputMetrics = taskContext.taskMetrics().inputMetrics

            override def hasNext =
              if (!overflow) {
                if (step > 0) {
                  number < safePartitionEnd
                } else {
                  number > safePartitionEnd
                }
              } else false

            override def next() = {
              val ret = number
              number += step
              if (number < ret ^ step < 0) {
                // we have Long.MaxValue + Long.MaxValue < Long.MaxValue
                // and Long.MinValue + Long.MinValue > Long.MinValue, so iff the step causes a step
                // back, we are pretty sure that we have an overflow.
                overflow = true
              }

              numOutputRows += 1
              inputMetrics.incRecordsRead(1)
              unsafeRow.setLong(0, ret)
              unsafeRow
            }
          }
          new InterruptibleIterator(taskContext, iter)
        }
    }
  }

  override def simpleString(maxFields: Int): String = {
    s"Range ($start, $end, step=$step, splits=$numSlices)"

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/991836.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号