Permalink
Browse files

More query compiler performance improvements:

An important take-away from these improvements is that it is generally
cheaper to broadly discard Path types when a NominalType has been
invalidated and rely on inference to rebuild them instead of performing
expensive type transformations.

Even with the extra transformations for the improved discriminator
column handling, performance is now >30% better than in 3.0.
  • Loading branch information...
szeiger committed Aug 20, 2015
1 parent b87c994 commit b01e539b19cab7de5e2a127879bacaf37c310288
@@ -8,7 +8,7 @@ import scala.collection.mutable.HashMap
/** Test query compiler performance with all queries from NewQuerySemanticsTest */
object CompilerBenchmark {
val RUNS = 10
val RUNS = 50
val COUNT_CREATE = 1000
val COUNT_TONODE = 1000
val COUNT_COMPILE = 10
@@ -46,7 +46,7 @@ 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 = Seq((generator, from))
def generators = Vector((generator, from))
override def getDumpInfo = super.getDumpInfo.copy(mainInfo = "")
protected[this] def rebuildWithSymbols(gen: IndexedSeq[TermSymbol]) = copy(generator = gen(0))
def withInferredType(scope: Type.Scope, typeChildren: Boolean): Self = {
@@ -81,7 +81,8 @@ final case class ParameterSwitch(cases: IndexedSeq[((Any => Boolean), Node)], de
copy(cases = (cases, ch).zipped.map { (c, n) => (c._1, n) }, default = ch.last)
protected def buildType = default.nodeType
def nodeMapServerSide(keepType: Boolean, r: Node => Node): Self = {
val this2 = mapOrNone(children)(r).map(rebuild).getOrElse(this)
val ch2 = mapOrNull(children)(r)
val this2 = if(ch2 eq null) this else rebuild(ch2)
if(keepType && hasType) this2 :@ nodeType
else this2
}
@@ -44,7 +44,7 @@ final case class Comprehension(sym: TermSymbol, from: Node, select: Node, where:
offset = newOffset.headOption
)
}
def generators = Seq((sym, from))
def generators = Vector((sym, from))
override def getDumpInfo = super.getDumpInfo.copy(mainInfo = "")
protected[this] def rebuildWithSymbols(gen: IndexedSeq[TermSymbol]) = copy(sym = gen.head)
def withInferredType(scope: Type.Scope, typeChildren: Boolean): Self = {
@@ -56,25 +56,25 @@ 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 = mapOrNone(o)(_.infer(genScope, typeChildren))
val o2 = mapOrNull(o)(_.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.isEmpty && h2.isEmpty && fetch2.isEmpty && offset2.isEmpty
val same = (f2 eq from) && (s2 eq select) && w2.isEmpty && g2.isEmpty && (o2 eq null) && h2.isEmpty && fetch2.isEmpty && offset2.isEmpty
val newType =
if(!hasType) CollectionType(f2.nodeType.asCollectionType.cons, s2.nodeType.asCollectionType.elementType)
else nodeType
if(same && newType == nodeType) this else {
copy(
from = f2,
select = s2,
where = w2.map(_.headOption).getOrElse(where),
groupBy = g2.map(_.headOption).getOrElse(groupBy),
orderBy = o2.map(o2 => (orderBy, o2).zipped.map { case ((_, o), n) => (n, o) }).getOrElse(orderBy),
having = h2.map(_.headOption).getOrElse(having),
fetch = fetch2.map(_.headOption).getOrElse(fetch),
offset = offset2.map(_.headOption).getOrElse(offset)
where = w2.orElse(where),
groupBy = g2.orElse(groupBy),
orderBy = if(o2 eq null) orderBy else (orderBy, o2).zipped.map { case ((_, o), n) => (n, o) },
having = h2.orElse(having),
fetch = fetch2.orElse(fetch),
offset = offset2.orElse(offset)
) :@ newType
}
}
@@ -1,5 +1,6 @@
package slick.ast
import scala.collection.mutable.ListBuffer
import scala.language.existentials
import scala.reflect.ClassTag
import slick.SlickException
@@ -28,15 +29,24 @@ trait Node extends Dumpable {
/** Rebuild this node with new child nodes unless all children are identical to the current ones,
* in which case this node is returned. */
final def withChildren(ch: IndexedSeq[Node]): Self =
if((children, ch).zipped.forall(_ eq _)) this else rebuild(ch)
final def withChildren(ch2: IndexedSeq[Node]): Self = {
val ch = children
val len = ch.length
var i = 0
while(i < len) {
if(ch(i) ne ch2(i)) return rebuild(ch2)
i += 1
}
this
}
/** Apply a mapping function to all children of this node and recreate the node with the new
* children. If all new children are identical to the old ones, this node is returned. If
* ``keepType`` is true, the type of this node is kept even when the children have changed. */
final def mapChildren(f: Node => Node, keepType: Boolean = false): Self = if(isInstanceOf[NullaryNode]) this else {
val n: Self = mapOrNone(children)(f).fold(this: Self)(rebuild)
if(_type == UnassignedType || !keepType) n else (n :@ _type).asInstanceOf[Self]
def mapChildren(f: Node => Node, keepType: Boolean = false): Self = {
val ch2 = mapOrNull(children)(f)
val n: Self = if(ch2 eq null) this else rebuild(ch2)
if(!keepType || (_type eq UnassignedType)) n else (n :@ _type).asInstanceOf[Self]
}
/** The current type of this node. */
@@ -178,19 +188,36 @@ trait BinaryNode extends Node {
lazy val children = Vector(left, right)
protected[this] final def rebuild(ch: IndexedSeq[Node]): Self = rebuild(ch(0), ch(1))
protected[this] def rebuild(left: Node, right: Node): Self
override final def mapChildren(f: Node => Node, keepType: Boolean = false): Self = {
val l = left
val r = right
val l2 = f(l)
val r2 = f(r)
val n: Self = if((l eq l2) && (r eq r2)) this else rebuild(l2, r2)
val _type = peekType
if(!keepType || (_type eq UnassignedType)) n else (n :@ _type).asInstanceOf[Self]
}
}
trait UnaryNode extends Node {
def child: Node
lazy val children = Vector(child)
protected[this] final def rebuild(ch: IndexedSeq[Node]): Self = rebuild(ch(0))
protected[this] def rebuild(child: Node): Self
override final def mapChildren(f: Node => Node, keepType: Boolean = false): Self = {
val ch = child
val ch2 = f(child)
val n: Self = if(ch2 eq ch) this else rebuild(ch2)
val _type = peekType
if(!keepType || (_type eq UnassignedType)) n else (n :@ _type).asInstanceOf[Self]
}
}
trait NullaryNode extends Node {
val children = Vector.empty
protected[this] final def rebuild(ch: IndexedSeq[Node]): Self = rebuild
protected[this] def rebuild: Self
override final def mapChildren(f: Node => Node, keepType: Boolean = false): Self = this
}
/** An expression that represents a plain value lifted into a Query. */
@@ -236,7 +263,7 @@ object Subquery {
abstract class FilteredQuery extends Node {
protected[this] def generator: TermSymbol
def from: Node
def generators = Seq((generator, from))
def generators = Vector((generator, from))
override def getDumpInfo = super.getDumpInfo.copy(mainInfo = this match {
case p: Product => p.productIterator.filterNot(n => n.isInstanceOf[Node] || n.isInstanceOf[Symbol]).mkString(", ")
case _ => ""
@@ -308,7 +335,7 @@ final case class GroupBy(fromGen: TermSymbol, from: Node, by: Node, identity: Ty
override def childNames = Seq("from "+fromGen, "by")
protected[this] def rebuild(left: Node, right: Node) = copy(from = left, by = right)
protected[this] def rebuildWithSymbols(gen: IndexedSeq[TermSymbol]) = copy(fromGen = gen(0))
def generators = Seq((fromGen, from))
def generators = Vector((fromGen, from))
override def getDumpInfo = super.getDumpInfo.copy(mainInfo = identity.toString)
def withInferredType(scope: Type.Scope, typeChildren: Boolean): Self = {
val from2 = from.infer(scope, typeChildren)
@@ -355,7 +382,7 @@ final case class Join(leftGen: TermSymbol, rightGen: TermSymbol, left: Node, rig
protected[this] def rebuild(ch: IndexedSeq[Node]) = copy(left = ch(0), right = ch(1), on = ch(2))
override def childNames = Seq("left "+leftGen, "right "+rightGen, "on")
override def getDumpInfo = super.getDumpInfo.copy(mainInfo = jt.toString)
def generators = Seq((leftGen, left), (rightGen, right))
def generators = Vector((leftGen, left), (rightGen, right))
protected[this] def rebuildWithSymbols(gen: IndexedSeq[TermSymbol]) =
copy(leftGen = gen(0), rightGen = gen(1))
def withInferredType(scope: Type.Scope, typeChildren: Boolean): Self = {
@@ -394,14 +421,15 @@ final case class Bind(generator: TermSymbol, from: Node, select: Node) extends B
def right = select
override def childNames = Seq("from "+generator, "select")
protected[this] def rebuild(left: Node, right: Node) = copy(from = left, select = right)
def generators = Seq((generator, from))
def generators = Vector((generator, from))
override def getDumpInfo = super.getDumpInfo.copy(mainInfo = "")
protected[this] def rebuildWithSymbols(gen: IndexedSeq[TermSymbol]) = copy(generator = gen(0))
def withInferredType(scope: Type.Scope, typeChildren: Boolean): Self = {
val from2 = from.infer(scope, typeChildren)
val from2Type = from2.nodeType.asCollectionType
val select2 = select.infer(scope + (generator -> from2Type.elementType), typeChildren)
withChildren(Vector(from2, select2)) :@ (
val withCh = if((from2 eq from) && (select2 eq select)) this else rebuild(from2, select2)
withCh :@ (
if(!hasType) CollectionType(from2Type.cons, select2.nodeType.asCollectionType.elementType)
else nodeType)
}
@@ -416,7 +444,7 @@ final case class Aggregate(sym: TermSymbol, from: Node, select: Node) extends Bi
def right = select
override def childNames = Seq("from "+sym, "select")
protected[this] def rebuild(left: Node, right: Node) = copy(from = left, select = right)
def generators = Seq((sym, from))
def generators = Vector((sym, from))
override def getDumpInfo = super.getDumpInfo.copy(mainInfo = "")
protected[this] def rebuildWithSymbols(gen: IndexedSeq[TermSymbol]) = copy(sym = gen(0))
def withInferredType(scope: Type.Scope, typeChildren: Boolean): Self = {
@@ -433,7 +461,7 @@ final case class TableExpansion(generator: TermSymbol, table: Node, columns: Nod
def right = columns
override def childNames = Seq("table "+generator, "columns")
protected[this] def rebuild(left: Node, right: Node) = copy(table = left, columns = right)
def generators = Seq((generator, table))
def generators = Vector((generator, table))
override def getDumpInfo = super.getDumpInfo.copy(mainInfo = "")
protected[this] def rebuildWithSymbols(gen: IndexedSeq[TermSymbol]) = copy(generator = gen(0))
def withInferredType(scope: Type.Scope, typeChildren: Boolean): Self = {
@@ -445,6 +473,8 @@ final case class TableExpansion(generator: TermSymbol, table: Node, columns: Nod
trait PathElement extends Node {
def sym: TermSymbol
def pathString: String
def untypedPath: PathElement
}
/** An expression that selects a field in another expression. */
@@ -459,6 +489,11 @@ final case class Select(in: Node, field: TermSymbol) extends PathElement with Un
case None => super.getDumpInfo
}
protected def buildType = in.nodeType.select(field)
def pathString = in.asInstanceOf[PathElement].pathString+"."+field
def untypedPath = {
val in2 = in.asInstanceOf[PathElement].untypedPath
if(in2 eq in) untyped else Select(in2, field)
}
}
/** A function call expression. */
@@ -479,6 +514,8 @@ final case class Ref(sym: TermSymbol) extends PathElement with NullaryNode {
}
}
def rebuild = copy()
def pathString = sym.toString
def untypedPath = untyped
}
/** A constructor/extractor for nested Selects starting at a Ref so that, for example,
@@ -488,10 +525,20 @@ object Path {
case s :: Nil => Ref(s)
case s :: l => Select(apply(l), s)
}
def unapply(n: PathElement): Option[List[TermSymbol]] = n match {
case Ref(sym) => Some(List(sym))
case Select(in: PathElement, s) => unapply(in).map(l => s :: l)
case _ => None
def unapply(n: PathElement): Option[List[TermSymbol]] = {
var l = new ListBuffer[TermSymbol]
var el: Node = n
while(el.isInstanceOf[Select]) {
val sel = el.asInstanceOf[Select]
l += sel.sym
el = sel.child
}
el match {
case Ref(sym) =>
l += sym
Some(l.toList)
case _ => None
}
}
def toString(path: Seq[TermSymbol]): String = path.reverseIterator.mkString("Path ", ".", "")
def toString(s: Select): String = s match {
@@ -503,8 +550,24 @@ object Path {
/** A constructor/extractor for nested Selects starting at a Ref so that, for example,
* `a :: b :: c :: Nil` corresponds to path `a.b.c`. */
object FwdPath {
def apply(ch: List[TermSymbol]) = Path(ch.reverse)
def unapply(n: PathElement): Option[List[TermSymbol]] = Path.unapply(n).map(_.reverse)
def apply(ch: List[TermSymbol]): PathElement = {
var p: PathElement = Ref(ch.head)
ch.tail.foreach { sym => p = Select(p, sym) }
p
}
def unapply(n: PathElement): Option[List[TermSymbol]] = {
var l: List[TermSymbol] = Nil
var el: Node = n
while(el.isInstanceOf[Select]) {
val sel = el.asInstanceOf[Select]
l = sel.sym :: l
el = sel.child
}
el match {
case Ref(sym) => Some(sym :: l)
case _ => None
}
}
def toString(path: Seq[TermSymbol]): String = path.mkString("Path ", ".", "")
}
@@ -49,7 +49,7 @@ class AnonSymbol extends TermSymbol {
/** A Node which introduces Symbols. */
trait DefNode extends Node {
def generators: Seq[(TermSymbol, Node)]
def generators: IndexedSeq[(TermSymbol, Node)]
protected[this] def rebuildWithSymbols(gen: IndexedSeq[TermSymbol]): Node
final def mapScopedChildren(f: (Option[TermSymbol], Node) => Node): Self with DefNode = {
@@ -59,8 +59,10 @@ trait DefNode extends Node {
if((all, mapped).zipped.map((a, m) => a._2 eq m).contains(false)) rebuild(mapped).asInstanceOf[Self with DefNode]
else this
}
final def mapSymbols(f: TermSymbol => TermSymbol): Node =
mapOrNone(generators.map(_._1))(f).fold[Node](this) { s => rebuildWithSymbols(s.toIndexedSeq) }
final def mapSymbols(f: TermSymbol => TermSymbol): Node = {
val s2 = mapOrNull(generators.map(_._1))(f)
if(s2 eq null) this else rebuildWithSymbols(s2)
}
}
/** Provides names for symbols */
@@ -50,11 +50,10 @@ final case class StructType(elements: IndexedSeq[(TermSymbol, Type)]) extends Ty
lazy val symbolToIndex: Map[TermSymbol, Int] =
elements.zipWithIndex.map { case ((sym, _), idx) => (sym, idx) }(collection.breakOut)
def children: IndexedSeq[Type] = elements.map(_._2)
def mapChildren(f: Type => Type): StructType =
mapOrNone(elements.map(_._2))(f) match {
case Some(types2) => StructType((elements, types2).zipped.map((e, t) => (e._1, t)))
case None => this
}
def mapChildren(f: Type => Type): StructType = {
val ch2 = mapOrNull(elements.map(_._2))(f)
if(ch2 eq null) this else StructType((elements, ch2).zipped.map((e, t) => (e._1, t)))
}
override def select(sym: TermSymbol) = sym match {
case ElementSymbol(idx) => elements(idx-1)._2
case _ => elements.find(x => x._1 == sym).map(_._2).getOrElse(super.select(sym))
@@ -109,11 +108,10 @@ object OptionType {
final case class ProductType(elements: IndexedSeq[Type]) extends Type {
override def toString = "(" + elements.mkString(", ") + ")"
def mapChildren(f: Type => Type): ProductType =
mapOrNone(elements)(f) match {
case Some(e2) => ProductType(e2)
case None => this
}
def mapChildren(f: Type => Type): ProductType = {
val ch2 = mapOrNull(elements)(f)
if(ch2 eq null) this else ProductType(ch2)
}
override def select(sym: TermSymbol) = sym match {
case ElementSymbol(i) if i <= elements.length => elements(i-1)
case _ => super.select(sym)
@@ -295,8 +293,6 @@ class TypeUtil(val tpe: Type) extends AnyVal {
b
}
def collectAll[T](pf: PartialFunction[Type, Seq[T]]): Iterable[T] = collect[Seq[T]](pf).flatten
def containsSymbol(tss: scala.collection.Set[TypeSymbol]): Boolean = {
if(tss.isEmpty) false else tpe match {
case NominalType(ts, exp) => tss.contains(ts) || exp.containsSymbol(tss)
Oops, something went wrong.

0 comments on commit b01e539

Please sign in to comment.