From 4bb0a1fe875ad64665ab0fd1a53104e52ebf56a7 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Fri, 29 Sep 2017 22:20:32 +0200 Subject: [PATCH 1/3] Avoid expensive computations in Stats.record calls When `Stats.enabled` is false, `Stats.record` should be free, but before this commit this wasn't the case: since the `n` passed to `record` was by-value, it had to be evaluated, this is very expensive for calls like: record("retained typed trees after typer", unit.tpdTree.treeSize) Before this commit, we got after inlining: val n = unit.tpdTree.treeSize Now we get: def n = unit.tpdTree.treeSize it'd be even nicer if we didn't generate anything at all, but this shouldn't have much impact on running time. --- compiler/src/dotty/tools/dotc/util/Stats.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/util/Stats.scala b/compiler/src/dotty/tools/dotc/util/Stats.scala index 970b44248d70..bb2f2dff8063 100644 --- a/compiler/src/dotty/tools/dotc/util/Stats.scala +++ b/compiler/src/dotty/tools/dotc/util/Stats.scala @@ -21,7 +21,7 @@ import collection.mutable } @inline - def record(fn: String, n: Int = 1) = + def record(fn: => String, n: => Int = 1) = if (enabled) doRecord(fn, n) private def doRecord(fn: String, n: Int) = From 5f0ed19d45818db33b31442bfc686b534dacb259 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Fri, 29 Sep 2017 23:58:27 +0200 Subject: [PATCH 2/3] Optimize util.HashSet Using "%" to find the index of an entry in the backing array is expensive, by restricting our backing array size to be a power of two we can use "&" instead. This is similar to what we do in the implementation of MutableScope (note: should we try to avoid code duplication between util.HashSet and MutableScope?). --- compiler/src/dotty/tools/dotc/config/Config.scala | 6 ++++-- compiler/src/dotty/tools/dotc/core/TypeOps.scala | 2 +- compiler/src/dotty/tools/dotc/util/HashSet.scala | 8 ++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index d7980b55f36a..2e778a9db81a 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -147,8 +147,10 @@ object Config { /** Initial size of superId table */ final val InitialSuperIdsSize = 4096 - /** Initial capacity of uniques HashMap */ - final val initialUniquesCapacity = 40000 + /** Initial capacity of uniques HashMap. + * Note: This MUST BE a power of two to work with util.HashSet + */ + final val initialUniquesCapacity = 65536 /** How many recursive calls to NamedType#underlying are performed before logging starts. */ final val LogPendingUnderlyingThreshold = 50 diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index ba3b049d6f18..2b0be7e6e19e 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -131,7 +131,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. /** a faster version of cs1 intersect cs2 */ def intersect(cs1: List[ClassSymbol], cs2: List[ClassSymbol]): List[ClassSymbol] = { - val cs2AsSet = new util.HashSet[ClassSymbol](100) + val cs2AsSet = new util.HashSet[ClassSymbol](128) cs2.foreach(cs2AsSet.addEntry) cs1.filter(cs2AsSet.contains) } diff --git a/compiler/src/dotty/tools/dotc/util/HashSet.scala b/compiler/src/dotty/tools/dotc/util/HashSet.scala index ff470ef5dc26..c6fa4c9e84cc 100644 --- a/compiler/src/dotty/tools/dotc/util/HashSet.scala +++ b/compiler/src/dotty/tools/dotc/util/HashSet.scala @@ -2,7 +2,7 @@ package dotty.tools.dotc.util /** A hash set that allows some privileged protected access to its internals */ -class HashSet[T >: Null <: AnyRef](initialCapacity: Int, loadFactor: Float = 0.25f) extends Set[T] { +class HashSet[T >: Null <: AnyRef](powerOfTwoInitialCapacity: Int, loadFactor: Float = 0.25f) extends Set[T] { private var used: Int = _ private var limit: Int = _ private var table: Array[AnyRef] = _ @@ -20,11 +20,11 @@ class HashSet[T >: Null <: AnyRef](initialCapacity: Int, loadFactor: Float = 0.2 /** Remove all elements from this set and set back to initial configuration */ def clear(): Unit = { used = 0 - allocate(initialCapacity) + allocate(powerOfTwoInitialCapacity) } - /** Turn hashode `x` into a table index */ - private def index(x: Int): Int = math.abs(x % table.length) + /** Turn hashcode `x` into a table index */ + private def index(x: Int): Int = x & (table.length - 1) /** Hashcode, can be overridden */ def hash(x: T): Int = x.hashCode From c47f0c244db1ed444adfd1f6d050b14a5645ccf9 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Sat, 30 Sep 2017 00:04:09 +0200 Subject: [PATCH 3/3] Optimize hot method hasRedefinedMethod This showed up while doing some CPU profiling, --- .../dotty/tools/dotc/transform/TreeTransform.scala | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeTransform.scala b/compiler/src/dotty/tools/dotc/transform/TreeTransform.scala index e1e3edb7380c..768a51cf4e47 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeTransform.scala @@ -210,9 +210,16 @@ object TreeTransforms { */ class NXTransformations { - private def hasRedefinedMethod(cls: Class[_], name: String): Boolean = - if (cls.getDeclaredMethods.exists(_.getName == name)) cls != classOf[TreeTransform] - else hasRedefinedMethod(cls.getSuperclass, name) + private def hasRedefinedMethod(cls: Class[_], name: String): Boolean = { + val clsMethods = cls.getDeclaredMethods + var i = clsMethods.length - 1 + while (i >= 0) { + if (clsMethods(i).getName == name) + return cls != classOf[TreeTransform] + i -= 1 + } + hasRedefinedMethod(cls.getSuperclass, name) + } /** Create an index array `next` of size one larger than the size of `transforms` such that * for each index i, `next(i)` is the smallest index j such that