Skip to content
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

Further query compiler performance improvements #1252

Merged
merged 3 commits into from Aug 27, 2015
Merged
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.
+838 −307
Diff settings

Always

Just for now

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.
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.