Further query compiler performance improvements #1252

Merged
merged 3 commits into from Aug 27, 2015
Jump to file or symbol
Failed to load files and symbols.
+838 −307
Diff settings

Always

Just for now

Viewing a subset of changes. View all

Use a custom collection implementation in the AST:

`ConstArray` is a collection-like wrapper for an Array, similar to the
standard mutable `ArrayBuffer` but immutable. For performance reasons it
does not inherit any collection traits but it offers a similar API.

The changes in this commit reduce the measured query compilation times
in `CompilerBenchmark`by about 25%.

It would be possible to take this one step further by making ConstArray
a value class, which provides a further measurable performance
improvement. However, a value class cannot currently implement proper
equality semantics, thus making such a collection-like class much harder
to use.
  • Loading branch information...
szeiger committed Aug 24, 2015
commit 2adb7c36874c41f068176570d3812b674463660e
@@ -576,7 +576,7 @@ class NewQuerySemanticsTest extends AsyncTest[RelationalTestDB] {
import slick.ast.Util._
val qc = new QueryCompiler(tdb.driver.queryCompiler.phases.takeWhile(_.name != "codeGen"))
val cs = qc.run(q.toNode)
- val found = cs.tree.collect { case c: Comprehension => c }.size
+ val found = cs.tree.collect { case c: Comprehension => c }.length
if(found != exp)
throw cs.symbolNamer.use(new SlickTreeException(s"Found $found Comprehension nodes, should be $exp",
cs.tree, mark = (_.isInstanceOf[Comprehension]), removeUnmarked = false))
@@ -3,6 +3,7 @@ package slick.ast
import slick.ast.Util._
import slick.ast.TypeUtil._
import slick.SlickException
+import slick.util.ConstArray
/**
* An operation which is expected to be run on the client side.
@@ -30,7 +31,7 @@ object ClientSideOp {
final case class First(val child: Node) extends UnaryNode with SimplyTypedNode with ClientSideOp {
type Self = First
protected[this] def rebuild(ch: Node) = copy(child = ch)
- protected def buildType = children.head.nodeType.asCollectionType.elementType
+ protected def buildType = child.nodeType.asCollectionType.elementType
def nodeMapServerSide(keepType: Boolean, r: Node => Node) = mapChildren(r, keepType)
}
@@ -46,9 +47,9 @@ final case class ResultSetMapping(generator: TermSymbol, from: Node, map: Node)
def right = map
override def childNames = Seq("from "+generator, "map")
protected[this] def rebuild(left: Node, right: Node) = copy(from = left, map = right)
- def generators = Vector((generator, from))
+ def generators = ConstArray((generator, from))
override def getDumpInfo = super.getDumpInfo.copy(mainInfo = "")
- protected[this] def rebuildWithSymbols(gen: IndexedSeq[TermSymbol]) = copy(generator = gen(0))
+ protected[this] def rebuildWithSymbols(gen: ConstArray[TermSymbol]) = copy(generator = gen(0))
def withInferredType(scope: Type.Scope, typeChildren: Boolean): Self = {
val from2 = from.infer(scope, typeChildren)
val (map2, newType) = from2.nodeType match {
@@ -59,7 +60,7 @@ final case class ResultSetMapping(generator: TermSymbol, from: Node, map: Node)
val map2 = map.infer(scope + (generator -> t), typeChildren)
(map2, map2.nodeType)
}
- withChildren(Vector(from2, map2)) :@ (if(!hasType) newType else nodeType)
+ withChildren(ConstArray[Node](from2, map2)) :@ (if(!hasType) newType else nodeType)
}
def nodeMapServerSide(keepType: Boolean, r: Node => Node) = {
val this2 = mapScopedChildren {
@@ -73,16 +74,17 @@ final case class ResultSetMapping(generator: TermSymbol, from: Node, map: Node)
/** A switch for special-cased parameters that needs to be interpreted in order
* to find the correct query string for the query arguments. */
-final case class ParameterSwitch(cases: IndexedSeq[((Any => Boolean), Node)], default: Node) extends SimplyTypedNode with ClientSideOp {
+final case class ParameterSwitch(cases: ConstArray[((Any => Boolean), Node)], default: Node) extends SimplyTypedNode with ClientSideOp {
type Self = ParameterSwitch
def children = cases.map(_._2) :+ default
- override def childNames = cases.map("[" + _._1 + "]") :+ "default"
- protected[this] def rebuild(ch: IndexedSeq[Node]): Self =
- copy(cases = (cases, ch).zipped.map { (c, n) => (c._1, n) }, default = ch.last)
+ override def childNames = cases.map("[" + _._1 + "]").toSeq :+ "default"
+ protected[this] def rebuild(ch: ConstArray[Node]): Self =
+ copy(cases = cases.zip(ch).map { case (c, n) => (c._1, n) }, default = ch.last)
protected def buildType = default.nodeType
def nodeMapServerSide(keepType: Boolean, r: Node => Node): Self = {
- val ch2 = mapOrNull(children)(r)
- val this2 = if(ch2 eq null) this else rebuild(ch2)
+ val ch = children
+ val ch2 = ch.endoMap(r)
+ val this2 = if(ch2 eq ch) this else rebuild(ch2)
if(keepType && hasType) this2 :@ nodeType
else this2
}
@@ -2,23 +2,24 @@ package slick.ast
import TypeUtil.typeToTypeUtil
import Util._
+import slick.util.ConstArray
/** A SQL comprehension */
final case class Comprehension(sym: TermSymbol, from: Node, select: Node, where: Option[Node] = None,
- groupBy: Option[Node] = None, orderBy: IndexedSeq[(Node, Ordering)] = Vector.empty,
+ groupBy: Option[Node] = None, orderBy: ConstArray[(Node, Ordering)] = ConstArray.empty,
having: Option[Node] = None,
fetch: Option[Node] = None, offset: Option[Node] = None) extends DefNode {
type Self = Comprehension
- val children = Vector(from, select) ++ where ++ groupBy ++ orderBy.map(_._1) ++ having ++ fetch ++ offset
+ lazy val children = (ConstArray.newBuilder() + from + select ++ where ++ groupBy ++ orderBy.map(_._1) ++ having ++ fetch ++ offset).result
override def childNames =
Seq("from "+sym, "select") ++
where.map(_ => "where") ++
groupBy.map(_ => "groupBy") ++
- orderBy.map("orderBy " + _._2) ++
+ orderBy.map("orderBy " + _._2).toSeq ++
having.map(_ => "having") ++
fetch.map(_ => "fetch") ++
offset.map(_ => "offset")
- protected[this] def rebuild(ch: IndexedSeq[Node]) = {
+ protected[this] def rebuild(ch: ConstArray[Node]) = {
val newFrom = ch(0)
val newSelect = ch(1)
val whereOffset = 2
@@ -38,15 +39,15 @@ final case class Comprehension(sym: TermSymbol, from: Node, select: Node, where:
select = newSelect,
where = newWhere.headOption,
groupBy = newGroupBy.headOption,
- orderBy = (orderBy, newOrderBy).zipped.map { case ((_, o), n) => (n, o) },
+ orderBy = orderBy.zip(newOrderBy).map { case ((_, o), n) => (n, o) },
having = newHaving.headOption,
fetch = newFetch.headOption,
offset = newOffset.headOption
)
}
- def generators = Vector((sym, from))
+ def generators = ConstArray((sym, from))
override def getDumpInfo = super.getDumpInfo.copy(mainInfo = "")
- protected[this] def rebuildWithSymbols(gen: IndexedSeq[TermSymbol]) = copy(sym = gen.head)
+ protected[this] def rebuildWithSymbols(gen: ConstArray[TermSymbol]) = copy(sym = gen.head)
def withInferredType(scope: Type.Scope, typeChildren: Boolean): Self = {
// Assign type to "from" Node and compute the resulting scope
val f2 = from.infer(scope, typeChildren)
@@ -56,12 +57,12 @@ final case class Comprehension(sym: TermSymbol, from: Node, select: Node, where:
val w2 = mapOrNone(where)(_.infer(genScope, typeChildren))
val g2 = mapOrNone(groupBy)(_.infer(genScope, typeChildren))
val o = orderBy.map(_._1)
- val o2 = mapOrNull(o)(_.infer(genScope, typeChildren))
+ val o2 = o.endoMap(_.infer(genScope, typeChildren))
val h2 = mapOrNone(having)(_.infer(genScope, typeChildren))
val fetch2 = mapOrNone(fetch)(_.infer(genScope, typeChildren))
val offset2 = mapOrNone(offset)(_.infer(genScope, typeChildren))
// Check if the nodes changed
- val same = (f2 eq from) && (s2 eq select) && w2.isEmpty && g2.isEmpty && (o2 eq null) && h2.isEmpty && fetch2.isEmpty && offset2.isEmpty
+ val same = (f2 eq from) && (s2 eq select) && w2.isEmpty && g2.isEmpty && (o2 eq o) && h2.isEmpty && fetch2.isEmpty && offset2.isEmpty
val newType =
if(!hasType) CollectionType(f2.nodeType.asCollectionType.cons, s2.nodeType.asCollectionType.elementType)
else nodeType
@@ -71,7 +72,7 @@ final case class Comprehension(sym: TermSymbol, from: Node, select: Node, where:
select = s2,
where = w2.orElse(where),
groupBy = g2.orElse(groupBy),
- orderBy = if(o2 eq null) orderBy else (orderBy, o2).zipped.map { case ((_, o), n) => (n, o) },
+ orderBy = if(o2 eq o) orderBy else orderBy.zip(o2).map { case ((_, o), n) => (n, o) },
having = h2.orElse(having),
fetch = fetch2.orElse(fetch),
offset = offset2.orElse(offset)
@@ -81,12 +82,12 @@ final case class Comprehension(sym: TermSymbol, from: Node, select: Node, where:
}
/** The row_number window function */
-final case class RowNumber(by: IndexedSeq[(Node, Ordering)] = Vector.empty) extends SimplyTypedNode {
+final case class RowNumber(by: ConstArray[(Node, Ordering)] = ConstArray.empty) extends SimplyTypedNode {
type Self = RowNumber
def buildType = ScalaBaseType.longType
lazy val children = by.map(_._1)
- protected[this] def rebuild(ch: IndexedSeq[Node]) =
+ protected[this] def rebuild(ch: ConstArray[Node]) =
copy(by = by.zip(ch).map{ case ((_, o), n) => (n, o) })
- override def childNames = by.zipWithIndex.map("by" + _._2)
+ override def childNames = by.zipWithIndex.map("by" + _._2).toSeq
override def getDumpInfo = super.getDumpInfo.copy(mainInfo = "")
}
@@ -1,25 +1,27 @@
package slick.ast
+import slick.util.ConstArray
+
/** Represents an Insert operation. */
final case class Insert(tableSym: TermSymbol, table: Node, linear: Node) extends BinaryNode with DefNode {
type Self = Insert
def left = table
def right = linear
override def childNames = Vector("table "+tableSym, "linear")
- def generators = Vector((tableSym, table))
+ def generators = ConstArray((tableSym, table))
def rebuild(l: Node, r: Node) = copy(table = l, linear = r)
- def rebuildWithSymbols(gen: IndexedSeq[TermSymbol]) = copy(tableSym = gen(0))
+ def rebuildWithSymbols(gen: ConstArray[TermSymbol]) = copy(tableSym = gen(0))
def withInferredType(scope: Type.Scope, typeChildren: Boolean): Self = {
val table2 = table.infer(scope, typeChildren)
val lin2 = linear.infer(scope + (tableSym -> table2.nodeType), typeChildren)
- withChildren(Vector(table2, lin2)) :@ (if(!hasType) lin2.nodeType else nodeType)
+ withChildren(ConstArray[Node](table2, lin2)) :@ (if(!hasType) lin2.nodeType else nodeType)
}
override def getDumpInfo = super.getDumpInfo.copy(mainInfo = "")
}
/** A column in an Insert operation. */
-final case class InsertColumn(children: IndexedSeq[Node], fs: FieldSymbol, buildType: Type) extends Node with SimplyTypedNode {
+final case class InsertColumn(children: ConstArray[Node], fs: FieldSymbol, buildType: Type) extends Node with SimplyTypedNode {
type Self = InsertColumn
- protected[this] def rebuild(ch: IndexedSeq[Node]) = copy(children = ch)
+ protected[this] def rebuild(ch: ConstArray[Node]) = copy(children = ch)
override def getDumpInfo = super.getDumpInfo.copy(mainInfo = fs.toString)
}
@@ -1,5 +1,7 @@
package slick.ast
+import slick.util.ConstArray
+
/**
* The standard library for query operators.
*/
@@ -104,15 +106,15 @@ class FunctionSymbol(val name: String) extends TermSymbol {
/** Match an Apply of this Symbol */
def unapplySeq(n: Node) = n match {
- case Apply(sym, ch) if sym eq this => Some(ch)
+ case Apply(sym, ch) if sym eq this => Some(ch.toSeq)
case _ => None
}
/** Create a typed Apply of this Symbol */
- def typed(tpe: Type, ch: Node*): Apply = Apply(this, ch.toIndexedSeq)(tpe)
+ def typed(tpe: Type, ch: Node*): Apply = Apply(this, ConstArray.from(ch))(tpe)
/** Create a typed Apply of this Symbol */
- def typed[T : ScalaBaseType](ch: Node*): Apply = Apply(this, ch.toIndexedSeq)(implicitly[ScalaBaseType[T]])
+ def typed[T : ScalaBaseType](ch: Node*): Apply = Apply(this, ConstArray.from(ch))(implicitly[ScalaBaseType[T]])
override def toString = "Function "+name
}
Oops, something went wrong.