-
Notifications
You must be signed in to change notification settings - Fork 29k
[SPARK-20822][SQL] Generate code to directly get value from ColumnVector for table cache #18747
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
8134e73
8ae2368
750b230
b367a70
a3646e3
34982c6
45014d7
c356ebe
fd8cdbd
a12d8da
db61b41
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,10 +19,10 @@ package org.apache.spark.sql.execution | |
|
|
||
| import org.apache.spark.sql.catalyst.expressions.UnsafeRow | ||
| import org.apache.spark.sql.catalyst.expressions.codegen.{CodegenContext, ExprCode} | ||
| import org.apache.spark.sql.execution.columnar.InMemoryTableScanExec | ||
| import org.apache.spark.sql.execution.columnar.InMemoryRelation | ||
| import org.apache.spark.sql.execution.metric.SQLMetrics | ||
| import org.apache.spark.sql.execution.vectorized.{ColumnarBatch, ColumnVector} | ||
| import org.apache.spark.sql.types.DataType | ||
| import org.apache.spark.sql.execution.vectorized.{ColumnarBatch, ColumnVector, WritableColumnVector} | ||
| import org.apache.spark.sql.types.{DataType, DataTypes} | ||
|
|
||
|
|
||
| /** | ||
|
|
@@ -31,7 +31,7 @@ import org.apache.spark.sql.types.DataType | |
| */ | ||
| private[sql] trait ColumnarBatchScan extends CodegenSupport { | ||
|
|
||
| val inMemoryTableScan: InMemoryTableScanExec = null | ||
| val columnIndexes: Array[Int] = null | ||
|
|
||
| def vectorTypes: Option[Seq[String]] = None | ||
|
|
||
|
|
@@ -84,25 +84,45 @@ private[sql] trait ColumnarBatchScan extends CodegenSupport { | |
| val columnarBatchClz = classOf[ColumnarBatch].getName | ||
| val batch = ctx.freshName("batch") | ||
| ctx.addMutableState(columnarBatchClz, batch, s"$batch = null;") | ||
| val cachedBatchClz = "org.apache.spark.sql.execution.columnar.CachedBatch" | ||
| val cachedBatch = ctx.freshName("cachedBatch") | ||
|
||
|
|
||
| val idx = ctx.freshName("batchIdx") | ||
| ctx.addMutableState("int", idx, s"$idx = 0;") | ||
| val colVars = output.indices.map(i => ctx.freshName("colInstance" + i)) | ||
| val columnVectorClzs = vectorTypes.getOrElse( | ||
| Seq.fill(colVars.size)(classOf[ColumnVector].getName)) | ||
| val columnAccessorClz = "org.apache.spark.sql.execution.columnar.ColumnAccessor" | ||
|
||
| val writableColumnVectorClz = classOf[WritableColumnVector].getName | ||
| val dataTypesClz = classOf[DataTypes].getName | ||
| val columnAssigns = colVars.zip(columnVectorClzs).zipWithIndex.map { | ||
| case ((name, columnVectorClz), i) => | ||
| ctx.addMutableState(columnVectorClz, name, s"$name = null;") | ||
| s"$name = ($columnVectorClz) $batch.column($i);" | ||
| val index = if (columnIndexes == null) i else columnIndexes(i) | ||
| s"$name = ($columnVectorClz) $batch.column($index);" + (if (columnIndexes == null) "" else { | ||
| val dt = output.attrs(i).dataType | ||
| s"\n$columnAccessorClz$$.MODULE$$.decompress(" + | ||
| s"$cachedBatch.buffers()[$index], ($writableColumnVectorClz) $name, " + | ||
| s"$dataTypesClz.$dt, $cachedBatch.numRows());" | ||
| }) | ||
| } | ||
|
|
||
| val assignBatch = if (columnIndexes == null) { | ||
| s"$batch = ($columnarBatchClz)$input.next();" | ||
| } else { | ||
| val inMemoryRelationClz = classOf[InMemoryRelation].getName | ||
| s""" | ||
| $cachedBatchClz $cachedBatch = ($cachedBatchClz)$input.next(); | ||
| $batch = $inMemoryRelationClz$$.MODULE$$.createColumn($cachedBatch); | ||
| """ | ||
| } | ||
| val nextBatch = ctx.freshName("nextBatch") | ||
| val nextBatchFuncName = ctx.addNewFunction(nextBatch, | ||
| s""" | ||
| |private void $nextBatch() throws java.io.IOException { | ||
| | long getBatchStart = System.nanoTime(); | ||
| | if ($input.hasNext()) { | ||
| | $batch = ($columnarBatchClz)$input.next(); | ||
| | $assignBatch | ||
| | $numOutputRows.add($batch.numRows()); | ||
| | $idx = 0; | ||
| | ${columnAssigns.mkString("", "\n", "\n")} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -282,6 +282,18 @@ case class InputAdapter(child: SparkPlan) extends UnaryExecNode with CodegenSupp | |
|
|
||
| object WholeStageCodegenExec { | ||
| val PIPELINE_DURATION_METRIC = "duration" | ||
|
|
||
| private def numOfNestedFields(dataType: DataType): Int = dataType match { | ||
| case dt: StructType => dt.fields.map(f => numOfNestedFields(f.dataType)).sum | ||
| case m: MapType => numOfNestedFields(m.keyType) + numOfNestedFields(m.valueType) | ||
| case a: ArrayType => numOfNestedFields(a.elementType) | ||
| case u: UserDefinedType[_] => numOfNestedFields(u.sqlType) | ||
| case _ => 1 | ||
| } | ||
|
|
||
| def isTooManyFields(conf: SQLConf, dataType: DataType): Boolean = { | ||
| numOfNestedFields(dataType) > conf.wholeStageMaxNumFields | ||
| } | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -490,22 +502,14 @@ case class CollapseCodegenStages(conf: SQLConf) extends Rule[SparkPlan] { | |
| case _ => true | ||
| } | ||
|
|
||
| private def numOfNestedFields(dataType: DataType): Int = dataType match { | ||
| case dt: StructType => dt.fields.map(f => numOfNestedFields(f.dataType)).sum | ||
| case m: MapType => numOfNestedFields(m.keyType) + numOfNestedFields(m.valueType) | ||
| case a: ArrayType => numOfNestedFields(a.elementType) | ||
| case u: UserDefinedType[_] => numOfNestedFields(u.sqlType) | ||
| case _ => 1 | ||
| } | ||
|
|
||
| private def supportCodegen(plan: SparkPlan): Boolean = plan match { | ||
| case plan: CodegenSupport if plan.supportCodegen => | ||
| val willFallback = plan.expressions.exists(_.find(e => !supportCodegen(e)).isDefined) | ||
| // the generated code will be huge if there are too many columns | ||
| val hasTooManyOutputFields = | ||
| numOfNestedFields(plan.schema) > conf.wholeStageMaxNumFields | ||
| WholeStageCodegenExec.isTooManyFields(conf, plan.schema) | ||
| val hasTooManyInputFields = | ||
| plan.children.map(p => numOfNestedFields(p.schema)).exists(_ > conf.wholeStageMaxNumFields) | ||
| plan.children.find(p => WholeStageCodegenExec.isTooManyFields(conf, p.schema)).isDefined | ||
|
||
| !willFallback && !hasTooManyOutputFields && !hasTooManyInputFields | ||
| case _ => false | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,7 +26,9 @@ import org.apache.spark.sql.catalyst.analysis.MultiInstanceRelation | |
| import org.apache.spark.sql.catalyst.expressions._ | ||
| import org.apache.spark.sql.catalyst.plans.logical | ||
| import org.apache.spark.sql.catalyst.plans.logical.Statistics | ||
| import org.apache.spark.sql.execution.SparkPlan | ||
| import org.apache.spark.sql.execution.{SparkPlan, WholeStageCodegenExec} | ||
| import org.apache.spark.sql.execution.vectorized.{ColumnarBatch, ColumnVector, OnHeapColumnVector} | ||
| import org.apache.spark.sql.types._ | ||
| import org.apache.spark.storage.StorageLevel | ||
| import org.apache.spark.util.LongAccumulator | ||
|
|
||
|
|
@@ -39,6 +41,16 @@ object InMemoryRelation { | |
| child: SparkPlan, | ||
| tableName: Option[String]): InMemoryRelation = | ||
| new InMemoryRelation(child.output, useCompression, batchSize, storageLevel, child, tableName)() | ||
|
|
||
| def createColumn(cachedColumnarBatch: CachedBatch): ColumnarBatch = { | ||
| val rowCount = cachedColumnarBatch.numRows | ||
| val schema = cachedColumnarBatch.schema | ||
| val columnVectors = OnHeapColumnVector.allocateColumns(rowCount, schema) | ||
| val columnarBatch = new ColumnarBatch( | ||
| schema, columnVectors.asInstanceOf[Array[ColumnVector]], rowCount) | ||
| columnarBatch.setNumRows(rowCount) | ||
| return columnarBatch | ||
| } | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -48,9 +60,11 @@ object InMemoryRelation { | |
| * @param numRows The total number of rows in this batch | ||
| * @param buffers The buffers for serialized columns | ||
| * @param stats The stat of columns | ||
| * @param schema The schema of columns | ||
| */ | ||
| private[columnar] | ||
| case class CachedBatch(numRows: Int, buffers: Array[Array[Byte]], stats: InternalRow) | ||
| case class CachedBatch( | ||
| numRows: Int, buffers: Array[Array[Byte]], stats: InternalRow, schema: StructType) | ||
|
|
||
| case class InMemoryRelation( | ||
| output: Seq[Attribute], | ||
|
|
@@ -63,6 +77,23 @@ case class InMemoryRelation( | |
| val batchStats: LongAccumulator = child.sqlContext.sparkContext.longAccumulator) | ||
| extends logical.LeafNode with MultiInstanceRelation { | ||
|
|
||
| /** | ||
| * If true, get data from ColumnVector in ColumnarBatch, which are generally faster. | ||
| * If false, get data from UnsafeRow build from ColumnVector | ||
| */ | ||
| private[columnar] val useColumnarBatches: Boolean = { | ||
|
||
| // In the initial implementation, for ease of review | ||
| // support only primitive data types and # of fields is less than wholeStageMaxNumFields | ||
| val schema = StructType.fromAttributes(child.output) | ||
| schema.fields.find(f => f.dataType match { | ||
| case BooleanType | ByteType | ShortType | IntegerType | LongType | | ||
| FloatType | DoubleType => false | ||
| case _ => true | ||
| }).isEmpty && | ||
| !WholeStageCodegenExec.isTooManyFields(conf, child.schema) && | ||
| children.find(p => WholeStageCodegenExec.isTooManyFields(conf, p.schema)).isEmpty | ||
| } | ||
|
|
||
| override protected def innerChildren: Seq[SparkPlan] = Seq(child) | ||
|
|
||
| override def producedAttributes: AttributeSet = outputSet | ||
|
|
@@ -87,6 +118,7 @@ case class InMemoryRelation( | |
|
|
||
| private def buildBuffers(): Unit = { | ||
| val output = child.output | ||
| val useColumnarBatch = useColumnarBatches | ||
| val cached = child.execute().mapPartitionsInternal { rowIterator => | ||
| new Iterator[CachedBatch] { | ||
| def next(): CachedBatch = { | ||
|
|
@@ -126,7 +158,8 @@ case class InMemoryRelation( | |
| columnBuilders.flatMap(_.columnStats.collectedStatistics)) | ||
| CachedBatch(rowCount, columnBuilders.map { builder => | ||
| JavaUtils.bufferToArray(builder.build()) | ||
| }, stats) | ||
| }, stats, | ||
| if (useColumnarBatch) StructType.fromAttributes(output) else null) | ||
| } | ||
|
|
||
| def hasNext: Boolean = rowIterator.hasNext | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,21 +23,37 @@ import org.apache.spark.sql.catalyst.dsl.expressions._ | |
| import org.apache.spark.sql.catalyst.expressions._ | ||
| import org.apache.spark.sql.catalyst.plans.QueryPlan | ||
| import org.apache.spark.sql.catalyst.plans.physical.{HashPartitioning, Partitioning} | ||
| import org.apache.spark.sql.execution.LeafExecNode | ||
| import org.apache.spark.sql.execution.metric.SQLMetrics | ||
| import org.apache.spark.sql.execution.{ColumnarBatchScan, LeafExecNode} | ||
| import org.apache.spark.sql.execution.vectorized.OnHeapColumnVector | ||
| import org.apache.spark.sql.types.UserDefinedType | ||
|
|
||
|
|
||
| case class InMemoryTableScanExec( | ||
| attributes: Seq[Attribute], | ||
| predicates: Seq[Expression], | ||
| @transient relation: InMemoryRelation) | ||
| extends LeafExecNode { | ||
| extends LeafExecNode with ColumnarBatchScan { | ||
|
|
||
| override protected def innerChildren: Seq[QueryPlan[_]] = Seq(relation) ++ super.innerChildren | ||
|
|
||
| override lazy val metrics = Map( | ||
| "numOutputRows" -> SQLMetrics.createMetric(sparkContext, "number of output rows")) | ||
| override def vectorTypes: Option[Seq[String]] = | ||
| Option(Seq.fill(attributes.length)(classOf[OnHeapColumnVector].getName)) | ||
|
|
||
| override val columnIndexes = | ||
| attributes.map(a => relation.output.map(o => o.exprId).indexOf(a.exprId)).toArray | ||
|
|
||
| override val supportCodegen: Boolean = relation.useColumnarBatches | ||
|
|
||
| override def inputRDDs(): Seq[RDD[InternalRow]] = { | ||
| if (supportCodegen) { | ||
|
||
| val buffers = relation.cachedColumnBuffers | ||
| // HACK ALERT: This is actually an RDD[CachedBatch]. | ||
| // We're taking advantage of Scala's type erasure here to pass these batches along. | ||
| Seq(buffers.asInstanceOf[RDD[InternalRow]]) | ||
|
||
| } else { | ||
| Seq() | ||
| } | ||
| } | ||
|
|
||
| override def output: Seq[Attribute] = attributes | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we remove these unnecessary imports?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, done