-
Notifications
You must be signed in to change notification settings - Fork 28.9k
[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 all commits
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 |
|---|---|---|
|
|
@@ -23,21 +23,66 @@ 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.types.UserDefinedType | ||
| import org.apache.spark.sql.execution.{ColumnarBatchScan, LeafExecNode, WholeStageCodegenExec} | ||
| import org.apache.spark.sql.execution.vectorized._ | ||
| import org.apache.spark.sql.types._ | ||
|
|
||
|
|
||
| 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)) | ||
|
|
||
| /** | ||
| * If true, get data from ColumnVector in ColumnarBatch, which are generally faster. | ||
| * If false, get data from UnsafeRow build from ColumnVector | ||
| */ | ||
| override val supportCodegen: Boolean = { | ||
| // In the initial implementation, for ease of review | ||
| // support only primitive data types and # of fields is less than wholeStageMaxNumFields | ||
| relation.schema.fields.forall(f => f.dataType match { | ||
| case BooleanType | ByteType | ShortType | IntegerType | LongType | | ||
| FloatType | DoubleType => true | ||
| case _ => false | ||
| }) && !WholeStageCodegenExec.isTooManyFields(conf, relation.schema) | ||
| } | ||
|
|
||
| private val columnIndices = | ||
| attributes.map(a => relation.output.map(o => o.exprId).indexOf(a.exprId)).toArray | ||
|
|
||
| private val relationSchema = relation.schema.toArray | ||
|
|
||
| private lazy val columnarBatchSchema = new StructType(columnIndices.map(i => relationSchema(i))) | ||
|
|
||
| private def createAndDecompressColumn(cachedColumnarBatch: CachedBatch): ColumnarBatch = { | ||
| val rowCount = cachedColumnarBatch.numRows | ||
| val columnVectors = OnHeapColumnVector.allocateColumns(rowCount, columnarBatchSchema) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any reason we use
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both can be used. I follow this default configuration since it is not easy to get |
||
| val columnarBatch = new ColumnarBatch( | ||
| columnarBatchSchema, columnVectors.asInstanceOf[Array[ColumnVector]], rowCount) | ||
| columnarBatch.setNumRows(rowCount) | ||
|
|
||
| for (i <- 0 until attributes.length) { | ||
| ColumnAccessor.decompress( | ||
| cachedColumnarBatch.buffers(columnIndices(i)), | ||
| columnarBatch.column(i).asInstanceOf[WritableColumnVector], | ||
| columnarBatchSchema.fields(i).dataType, rowCount) | ||
| } | ||
| columnarBatch | ||
| } | ||
|
|
||
| override def inputRDDs(): Seq[RDD[InternalRow]] = { | ||
| assert(supportCodegen) | ||
| val buffers = relation.cachedColumnBuffers | ||
| // HACK ALERT: This is actually an RDD[ColumnarBatch]. | ||
| // We're taking advantage of Scala's type erasure here to pass these batches along. | ||
| Seq(buffers.map(createAndDecompressColumn(_)).asInstanceOf[RDD[InternalRow]]) | ||
| } | ||
|
|
||
| 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 reuse the
OnHeapColumnVectorfor the cached batches? It's a little inefficient to create one column vector for each cached batch.Uh oh!
There was an error while loading. Please reload this page.
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.
I agree that we can improve efficiency if we can reuse the
OnHeapColumnVector.I think that it is not easy to reuse the
OnHeapColumnVectorbetween different cached batches.IIUC there is no point to know a cached batch will not be referenced. We rely the management of the lifetime on GC by creating
OnHeapColumnVectorevery time for each cached batch.If we reuse the
OnHeapColumnVector(i.e. keep a reference toOnHeapColumnVector), GC will not disposeOnHeapColumnVectoreven if the generated code will not use theOnHeapColumnVector. It means that uncompressed (huge) data would be alive for a long time. If we know the point where a cache batch will not be referenced, we could set null todatainOnHeapColumnVector.Thus, I currently create
OnHeapColumnVectorfor each cached batch. What do you think?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't we use
TaskContext.addTaskCompletionListener?Uh oh!
There was an error while loading. Please reload this page.
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.
Good idea. We could accomplish to set
nullinto adatafield (e.g.intData) inOnHeapColumnVector.intDataby registeringclear()method toTaskContext.addTaskCompletionListener.In that case, I realized that we would have to reallocate a large array for a
datafield inOnHeapColumnVector.intDataeach time. Do we still need to take care of efficiency of allocatingOnHeapColumnVectorwhose size is relatively smaller than the size of the large array?If it still makes sense, I will try to implement
clear()method and reallocating a large array that may introduce new API intoOnHeapColumnVector.What do you think?
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.
ok maybe leave it as a followup for now.
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.
I see. Let us make a follow-up PR in the future.