Skip to content
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,9 @@ public String toString() {
build.append(java.lang.Long.toHexString(Platform.getLong(baseObject, baseOffset + i)));
build.append(',');
}
build.deleteCharAt(build.length() - 1);
if (sizeInBytes > 0) {
build.deleteCharAt(build.length() - 1);
}
build.append(']');
return build.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ class CodegenContext {
*/
val references: mutable.ArrayBuffer[Any] = new mutable.ArrayBuffer[Any]()

/**
* Add an object to `references`, create a class member to access it.
*
* Returns the name of class member.
*/
def addReferenceObj(name: String, obj: Any, className: String = null): String = {
val term = freshName(name)
val idx = references.length
references += obj
val clsName = Option(className).getOrElse(obj.getClass.getName)
addMutableState(clsName, term, s"this.$term = ($clsName) references[$idx];")
term
}

/**
* Holding a list of generated columns as input of current operator, will be used by
* BoundReference to generate code.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ object GenerateMutableProjection extends CodeGenerator[Seq[Expression], () => Mu
// Can't call setNullAt on DecimalType, because we need to keep the offset
s"""
if (this.isNull_$i) {
${ctx.setColumn("mutableRow", e.dataType, i, null)};
${ctx.setColumn("mutableRow", e.dataType, i, "null")};
} else {
${ctx.setColumn("mutableRow", e.dataType, i, s"this.value_$i")};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

package org.apache.spark.sql.execution;

import java.io.IOException;

import scala.collection.Iterator;

import org.apache.spark.sql.catalyst.InternalRow;
Expand All @@ -34,7 +36,7 @@ public class BufferedRowIterator {
// used when there is no column in output
protected UnsafeRow unsafeRow = new UnsafeRow(0);

public boolean hasNext() {
public boolean hasNext() throws IOException {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why'd you change this to throw exceptions?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The KVIterator will throws IOException, we need to propagate it here.

if (currentRow == null) {
processNext();
}
Expand All @@ -56,7 +58,7 @@ public void setInput(Iterator<InternalRow> iter) {
*
* After it's called, if currentRow is still null, it means no more rows left.
*/
protected void processNext() {
protected void processNext() throws IOException {
if (input.hasNext()) {
currentRow = input.next();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.apache.spark.sql.catalyst.InternalRow
import org.apache.spark.sql.catalyst.expressions.{Attribute, BoundReference, Expression, LeafExpression}
import org.apache.spark.sql.catalyst.expressions.codegen._
import org.apache.spark.sql.catalyst.rules.Rule
import org.apache.spark.util.Utils

/**
* An interface for those physical operators that support codegen.
Expand All @@ -41,10 +42,15 @@ trait CodegenSupport extends SparkPlan {
*/
private var parent: CodegenSupport = null

/**
* Returns the RDD of InternalRow which generates the input rows.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you comment why this is useful?

*/
def upstream(): RDD[InternalRow]

/**
* Returns an input RDD of InternalRow and Java source code to process them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update comment.

*/
def produce(ctx: CodegenContext, parent: CodegenSupport): (RDD[InternalRow], String) = {
def produce(ctx: CodegenContext, parent: CodegenSupport): String = {
this.parent = parent
doProduce(ctx)
}
Expand All @@ -66,16 +72,40 @@ trait CodegenSupport extends SparkPlan {
* # call consume(), wich will call parent.doConsume()
* }
*/
protected def doProduce(ctx: CodegenContext): (RDD[InternalRow], String)
protected def doProduce(ctx: CodegenContext): String

/**
* Consume the columns generated from current SparkPlan, call it's parent or create an iterator.
* Consume the columns generated from current SparkPlan, call it's parent.
*/
protected def consume(ctx: CodegenContext, columns: Seq[ExprCode]): String = {
assert(columns.length == output.length)
parent.doConsume(ctx, this, columns)
def consume(ctx: CodegenContext, input: Seq[ExprCode], row: String = null): String = {
if (input != null) {
assert(input.length == output.length)
}
parent.consumeChild(ctx, this, input, row)
}

/**
* Consume the columns generated from it's child, call doConsume() or emit the rows.
*/
def consumeChild(
ctx: CodegenContext,
child: SparkPlan,
input: Seq[ExprCode],
row: String = null): String = {
if (row != null) {
ctx.currentVars = null
ctx.INPUT_ROW = row
val evals = child.output.zipWithIndex.map { case (attr, i) =>
BoundReference(i, attr.dataType, attr.nullable).gen(ctx)
}
s"""
| ${evals.map(_.code).mkString("\n")}
| ${doConsume(ctx, child, evals)}
""".stripMargin
} else {
doConsume(ctx, child, input)
}
}

/**
* Generate the Java source code to process the rows from child SparkPlan.
Expand All @@ -89,7 +119,9 @@ trait CodegenSupport extends SparkPlan {
* # call consume(), which will call parent.doConsume()
* }
*/
def doConsume(ctx: CodegenContext, child: SparkPlan, input: Seq[ExprCode]): String
protected def doConsume(ctx: CodegenContext, child: SparkPlan, input: Seq[ExprCode]): String = {
throw new UnsupportedOperationException
}
}


Expand All @@ -105,24 +137,23 @@ case class InputAdapter(child: SparkPlan) extends LeafNode with CodegenSupport {

override def supportCodegen: Boolean = true

override def doProduce(ctx: CodegenContext): (RDD[InternalRow], String) = {
override def upstream(): RDD[InternalRow] = {
child.execute()
}

override def doProduce(ctx: CodegenContext): String = {
val exprs = output.zipWithIndex.map(x => new BoundReference(x._2, x._1.dataType, true))
val row = ctx.freshName("row")
ctx.INPUT_ROW = row
ctx.currentVars = null
val columns = exprs.map(_.gen(ctx))
val code = s"""
s"""
| while (input.hasNext()) {
| InternalRow $row = (InternalRow) input.next();
| ${columns.map(_.code).mkString("\n")}
| ${consume(ctx, columns)}
| }
""".stripMargin
(child.execute(), code)
}

def doConsume(ctx: CodegenContext, child: SparkPlan, input: Seq[ExprCode]): String = {
throw new UnsupportedOperationException
}

override def doExecute(): RDD[InternalRow] = {
Expand Down Expand Up @@ -150,9 +181,11 @@ case class InputAdapter(child: SparkPlan) extends LeafNode with CodegenSupport {
* doProduce() ---> execute()
* |
* consume()
* doConsume() ------------|
* consumeChild() <-----------|
* |
* doConsume() <----- consume()
* doConsume()
* |
* consumeChild() <----- consume()
*
* SparkPlan A should override doProduce() and doConsume().
*
Expand All @@ -166,33 +199,36 @@ case class WholeStageCodegen(plan: CodegenSupport, children: Seq[SparkPlan])

override def doExecute(): RDD[InternalRow] = {
val ctx = new CodegenContext
val (rdd, code) = plan.produce(ctx, this)
val code = plan.produce(ctx, this)
val references = ctx.references.toArray
val source = s"""
public Object generate(Object[] references) {
return new GeneratedIterator(references);
return new GeneratedIterator(references);
}

class GeneratedIterator extends org.apache.spark.sql.execution.BufferedRowIterator {

private Object[] references;
${ctx.declareMutableStates()}
private Object[] references;
${ctx.declareMutableStates()}
${ctx.declareAddedFunctions()}

public GeneratedIterator(Object[] references) {
public GeneratedIterator(Object[] references) {
this.references = references;
${ctx.initMutableStates()}
}
}

protected void processNext() {
protected void processNext() throws java.io.IOException {
$code
}
}
}
"""
"""

// try to compile, helpful for debug
// println(s"${CodeFormatter.format(source)}")
CodeGenerator.compile(source)

rdd.mapPartitions { iter =>
plan.upstream().mapPartitions { iter =>

val clazz = CodeGenerator.compile(source)
val buffer = clazz.generate(references).asInstanceOf[BufferedRowIterator]
buffer.setInput(iter)
Expand All @@ -203,29 +239,47 @@ case class WholeStageCodegen(plan: CodegenSupport, children: Seq[SparkPlan])
}
}

override def doProduce(ctx: CodegenContext): (RDD[InternalRow], String) = {
override def upstream(): RDD[InternalRow] = {
throw new UnsupportedOperationException
}

override def doConsume(ctx: CodegenContext, child: SparkPlan, input: Seq[ExprCode]): String = {
if (input.nonEmpty) {
val colExprs = output.zipWithIndex.map { case (attr, i) =>
BoundReference(i, attr.dataType, attr.nullable)
}
// generate the code to create a UnsafeRow
ctx.currentVars = input
val code = GenerateUnsafeProjection.createCode(ctx, colExprs, false)
s"""
| ${code.code.trim}
| currentRow = ${code.value};
| return;
""".stripMargin
} else {
// There is no columns
override def doProduce(ctx: CodegenContext): String = {
throw new UnsupportedOperationException
}

override def consumeChild(
ctx: CodegenContext,
child: SparkPlan,
input: Seq[ExprCode],
row: String = null): String = {

if (row != null) {
// There is an UnsafeRow already
s"""
| currentRow = unsafeRow;
| currentRow = $row;
| return;
""".stripMargin
} else {
assert(input != null)
if (input.nonEmpty) {
val colExprs = output.zipWithIndex.map { case (attr, i) =>
BoundReference(i, attr.dataType, attr.nullable)
}
// generate the code to create a UnsafeRow
ctx.currentVars = input
val code = GenerateUnsafeProjection.createCode(ctx, colExprs, false)
s"""
| ${code.code.trim}
| currentRow = ${code.value};
| return;
""".stripMargin
} else {
// There is no columns
s"""
| currentRow = unsafeRow;
| return;
""".stripMargin
}
}
}

Expand All @@ -246,7 +300,7 @@ case class WholeStageCodegen(plan: CodegenSupport, children: Seq[SparkPlan])
builder.append(simpleString)
builder.append("\n")

plan.generateTreeString(depth + 1, lastChildren :+children.isEmpty :+ true, builder)
plan.generateTreeString(depth + 2, lastChildren :+ false :+ true, builder)
if (children.nonEmpty) {
children.init.foreach(_.generateTreeString(depth + 1, lastChildren :+ false, builder))
children.last.generateTreeString(depth + 1, lastChildren :+ true, builder)
Expand Down Expand Up @@ -286,13 +340,14 @@ private[sql] case class CollapseCodegenStages(sqlContext: SQLContext) extends Ru
case plan: CodegenSupport if supportCodegen(plan) &&
// Whole stage codegen is only useful when there are at least two levels of operators that
// support it (save at least one projection/iterator).
plan.children.exists(supportCodegen) =>
(Utils.isTesting || plan.children.exists(supportCodegen)) =>

var inputs = ArrayBuffer[SparkPlan]()
val combined = plan.transform {
case p if !supportCodegen(p) =>
inputs += p
InputAdapter(p)
val input = apply(p) // collapse them recursively
inputs += input
InputAdapter(input)
}.asInstanceOf[CodegenSupport]
WholeStageCodegen(combined, inputs)
}
Expand Down
Loading