Skip to content

Commit

Permalink
Use a custom collection implementation in the AST:
Browse files Browse the repository at this point in the history
`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 25, 2015
1 parent b48115a commit 2adb7c3
Show file tree
Hide file tree
Showing 44 changed files with 838 additions and 307 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
22 changes: 12 additions & 10 deletions slick/src/main/scala/slick/ast/ClientSideOp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}

Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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
}
Expand Down
27 changes: 14 additions & 13 deletions slick/src/main/scala/slick/ast/Comprehension.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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 = "")
}
12 changes: 7 additions & 5 deletions slick/src/main/scala/slick/ast/Insert.scala
Original file line number Diff line number Diff line change
@@ -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)
}
8 changes: 5 additions & 3 deletions slick/src/main/scala/slick/ast/Library.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package slick.ast

import slick.util.ConstArray

/**
* The standard library for query operators.
*/
Expand Down Expand Up @@ -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
}
Loading

0 comments on commit 2adb7c3

Please sign in to comment.