From 8284486a155f145bf61019fc0bd8b56b8d327ff2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 18 Jun 2012 17:20:14 +0200 Subject: Statistics reorganization Statistics were broken since the move to reflect.internal. They are now re-organized, made more robost and modular. --- .../scala/reflect/internal/BaseTypeSeqs.scala | 12 +- src/reflect/scala/reflect/internal/Symbols.scala | 18 +- src/reflect/scala/reflect/internal/Trees.scala | 10 + src/reflect/scala/reflect/internal/Types.scala | 86 ++++--- .../scala/reflect/internal/util/StatBase.scala | 97 -------- .../scala/reflect/internal/util/Statistics.scala | 261 ++++++++++++++++++--- 6 files changed, 312 insertions(+), 172 deletions(-) delete mode 100644 src/reflect/scala/reflect/internal/util/StatBase.scala (limited to 'src/reflect') diff --git a/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala b/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala index 5f78671012..fa758edf05 100644 --- a/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala +++ b/src/reflect/scala/reflect/internal/BaseTypeSeqs.scala @@ -8,7 +8,7 @@ package internal // todo implement in terms of BitSet import scala.collection.{ mutable, immutable } import math.max -import util.Statistics._ +import util.Statistics /** A base type sequence (BaseTypeSeq) is an ordered sequence spanning all the base types * of a type. It characterized by the following two laws: @@ -28,6 +28,7 @@ import util.Statistics._ trait BaseTypeSeqs { this: SymbolTable => import definitions._ + import BaseTypeSeqsStats._ protected def newBaseTypeSeq(parents: List[Type], elems: Array[Type]) = new BaseTypeSeq(parents, elems) @@ -38,8 +39,8 @@ trait BaseTypeSeqs { */ class BaseTypeSeq protected[BaseTypeSeqs] (private[BaseTypeSeqs] val parents: List[Type], private[BaseTypeSeqs] val elems: Array[Type]) { self => - incCounter(baseTypeSeqCount) - incCounter(baseTypeSeqLenTotal, elems.length) + Statistics.incCounter(baseTypeSeqCount) + Statistics.incCounter(baseTypeSeqLenTotal, elems.length) /** The number of types in the sequence */ def length: Int = elems.length @@ -231,3 +232,8 @@ trait BaseTypeSeqs { val CyclicInheritance = new Throwable } + +object BaseTypeSeqsStats { + val baseTypeSeqCount = Statistics.newCounter("#base type seqs") + val baseTypeSeqLenTotal = Statistics.newRelCounter("avg base type seq length", baseTypeSeqCount) +} diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 288eb63332..4b0ceeb86b 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -8,18 +8,17 @@ package internal import scala.collection.{ mutable, immutable } import scala.collection.mutable.ListBuffer -import util.Statistics._ +import util.Statistics import Flags._ trait Symbols extends api.Symbols { self: SymbolTable => import definitions._ + import SymbolsStats._ protected var ids = 0 val emptySymbolArray = new Array[Symbol](0) - def symbolCount = ids // statistics - protected def nextId() = { ids += 1; ids } /** Used for deciding in the IDE whether we can interrupt the compiler */ @@ -666,7 +665,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => def isClassLocalToConstructor = false final def isDerivedValueClass = - isClass && !hasFlag(PACKAGE | TRAIT) && + isClass && !hasFlag(PACKAGE | TRAIT) && info.firstParent.typeSymbol == AnyValClass && !isPrimitiveValueClass final def isMethodWithExtension = @@ -2713,7 +2712,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => } } - incCounter(typeSymbolCount) + Statistics.incCounter(typeSymbolCount) } implicit val TypeSymbolTag = ClassTag[TypeSymbol](classOf[TypeSymbol]) @@ -2929,7 +2928,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => override def children = childSet override def addChild(sym: Symbol) { childSet = childSet + sym } - incCounter(classSymbolCount) + Statistics.incCounter(classSymbolCount) } implicit val ClassSymbolTag = ClassTag[ClassSymbol](classOf[ClassSymbol]) @@ -3188,4 +3187,11 @@ trait Symbols extends api.Symbols { self: SymbolTable => def toList: List[TypeHistory] = this :: ( if (prev eq null) Nil else prev.toList ) } + + Statistics.newView("#symbols")(ids) +} + +object SymbolsStats { + val typeSymbolCount = Statistics.newCounter("#type symbols") + val classSymbolCount = Statistics.newCounter("#class symbols") } diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index 75bb0e6d49..e100e0c1a3 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -9,6 +9,7 @@ package internal import Flags._ import base.Attachments import collection.mutable.{ListBuffer, LinkedHashSet} +import util.Statistics trait Trees extends api.Trees { self: SymbolTable => @@ -18,6 +19,8 @@ trait Trees extends api.Trees { self: SymbolTable => val id = nodeCount // TODO: add to attachment? nodeCount += 1 + Statistics.incCounter(TreesStats.nodeByType, getClass) + @inline final def pos: Position = rawatt.pos def pos_=(pos: Position): Unit = rawatt = (rawatt withPos pos) def setPos(newpos: Position): this.type = { pos = newpos; this } @@ -1592,4 +1595,11 @@ trait Trees extends api.Trees { self: SymbolTable => implicit val TypeBoundsTreeTag = ClassTag[TypeBoundsTree](classOf[TypeBoundsTree]) implicit val ExistentialTypeTreeTag = ClassTag[ExistentialTypeTree](classOf[ExistentialTypeTree]) implicit val TypeTreeTag = ClassTag[TypeTree](classOf[TypeTree]) + + val treeNodeCount = Statistics.newView("#created tree nodes")(nodeCount) +} + +object TreesStats { + // statistics + val nodeByType = Statistics.newByClass("#created tree nodes by type")(Statistics.newCounter("")) } diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 8a4394bf1d..d4b895bcb4 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -13,8 +13,7 @@ import mutable.ListBuffer import Flags._ import scala.util.control.ControlThrowable import scala.annotation.tailrec -import util.Statistics._ -import language.postfixOps +import util.Statistics /* A standard type pattern match: case ErrorType => @@ -73,9 +72,7 @@ import language.postfixOps trait Types extends api.Types { self: SymbolTable => import definitions._ - - //statistics - def uniqueTypeCount = if (uniques == null) 0 else uniques.size + import TypesStats._ private var explainSwitch = false private final val emptySymbolSet = immutable.Set.empty[Symbol] @@ -681,8 +678,8 @@ trait Types extends api.Types { self: SymbolTable => if (isTrivial || phase.erasedTypes && pre.typeSymbol != ArrayClass) this else { // scala.tools.nsc.util.trace.when(pre.isInstanceOf[ExistentialType])("X "+this+".asSeenfrom("+pre+","+clazz+" = ") { - incCounter(asSeenFromCount) - val start = startTimer(asSeenFromNanos) + Statistics.incCounter(asSeenFromCount) + val start = Statistics.startTimer(asSeenFromNanos) val m = new AsSeenFromMap(pre.normalize, clazz) val tp = m apply this val tp1 = existentialAbstraction(m.capturedParams, tp) @@ -690,7 +687,7 @@ trait Types extends api.Types { self: SymbolTable => if (m.capturedSkolems.isEmpty) tp1 else deriveType(m.capturedSkolems, _.cloneSymbol setFlag CAPTURED)(tp1) - stopTimer(asSeenFromNanos, start) + Statistics.stopTimer(asSeenFromNanos, start) result } } @@ -828,26 +825,26 @@ trait Types extends api.Types { self: SymbolTable => } def stat_<:<(that: Type): Boolean = { - incCounter(subtypeCount) - val start = startTimer(subtypeNanos) + Statistics.incCounter(subtypeCount) + val start = Statistics.startTimer(subtypeNanos) val result = (this eq that) || (if (explainSwitch) explain("<:", isSubType, this, that) else isSubType(this, that, AnyDepth)) - stopTimer(subtypeNanos, start) + Statistics.stopTimer(subtypeNanos, start) result } /** Is this type a weak subtype of that type? True also for numeric types, i.e. Int weak_<:< Long. */ def weak_<:<(that: Type): Boolean = { - incCounter(subtypeCount) - val start = startTimer(subtypeNanos) + Statistics.incCounter(subtypeCount) + val start = Statistics.startTimer(subtypeNanos) val result = ((this eq that) || (if (explainSwitch) explain("weak_<:", isWeakSubType, this, that) else isWeakSubType(this, that))) - stopTimer(subtypeNanos, start) + Statistics.stopTimer(subtypeNanos, start) result } @@ -1020,8 +1017,8 @@ trait Types extends api.Types { self: SymbolTable => // See (t0851) for a situation where this happens. val suspension: List[TypeVar] = if (this.isGround) null else suspendTypeVarsInType(this) - incCounter(findMemberCount) - val start = startTimer(findMemberNanos) + Statistics.incCounter(findMemberCount) + val start = Statistics.startTimer(findMemberNanos) //Console.println("find member " + name.decode + " in " + this + ":" + this.baseClasses)//DEBUG var members: Scope = null @@ -1048,7 +1045,7 @@ trait Types extends api.Types { self: SymbolTable => !sym.isPrivateLocal || (bcs0.head.hasTransOwner(bcs.head)))) { if (name.isTypeName || stableOnly && sym.isStable) { - stopTimer(findMemberNanos, start) + Statistics.stopTimer(findMemberNanos, start) if (suspension ne null) suspension foreach (_.suspended = false) return sym } else if (member == NoSymbol) { @@ -1094,13 +1091,13 @@ trait Types extends api.Types { self: SymbolTable => } // while (!bcs.isEmpty) excluded = excludedFlags } // while (continue) - stopTimer(findMemberNanos, start) + Statistics.stopTimer(findMemberNanos, start) if (suspension ne null) suspension foreach (_.suspended = false) if (members eq null) { - if (member == NoSymbol) incCounter(noMemberCount) + if (member == NoSymbol) Statistics.incCounter(noMemberCount) member } else { - incCounter(multMemberCount) + Statistics.incCounter(multMemberCount) baseClasses.head.newOverloaded(this, members.toList) } } @@ -1185,7 +1182,7 @@ trait Types extends api.Types { self: SymbolTable => override def isVolatile = underlying.isVolatile override def widen: Type = underlying.widen override def baseTypeSeq: BaseTypeSeq = { - incCounter(singletonBaseTypeSeqCount) + Statistics.incCounter(singletonBaseTypeSeqCount) underlying.baseTypeSeq prepend this } override def isHigherKinded = false // singleton type classifies objects, thus must be kind * @@ -1536,7 +1533,7 @@ trait Types extends api.Types { self: SymbolTable => val bts = copyRefinedType(tpe.asInstanceOf[RefinedType], tpe.parents map varToParam, varToParam mapOver tpe.decls).baseTypeSeq tpe.baseTypeSeqCache = bts lateMap paramToVar } else { - incCounter(compoundBaseTypeSeqCount) + Statistics.incCounter(compoundBaseTypeSeqCount) tpe.baseTypeSeqCache = undetBaseTypeSeq tpe.baseTypeSeqCache = if (tpe.typeSymbol.isRefinementClass) tpe.memo(compoundBaseTypeSeq(tpe))(_.baseTypeSeq updateHead tpe.typeSymbol.tpe) @@ -2392,7 +2389,7 @@ trait Types extends api.Types { self: SymbolTable => if (period != currentPeriod) { tpe.baseTypeSeqPeriod = currentPeriod if (!isValidForBaseClasses(period)) { - incCounter(typerefBaseTypeSeqCount) + Statistics.incCounter(typerefBaseTypeSeqCount) tpe.baseTypeSeqCache = undetBaseTypeSeq tpe.baseTypeSeqCache = tpe.baseTypeSeqImpl } @@ -3702,7 +3699,7 @@ trait Types extends api.Types { self: SymbolTable => private var uniqueRunId = NoRunId protected def unique[T <: Type](tp: T): T = { - incCounter(rawTypeCount) + Statistics.incCounter(rawTypeCount) if (uniqueRunId != currentRunId) { uniques = util.HashSet[Type]("uniques", initialUniquesCapacity) uniqueRunId = currentRunId @@ -5104,7 +5101,7 @@ trait Types extends api.Types { self: SymbolTable => /** Do `tp1` and `tp2` denote equivalent types? */ def isSameType(tp1: Type, tp2: Type): Boolean = try { - incCounter(sametypeCount) + Statistics.incCounter(sametypeCount) subsametypeRecursions += 1 undoLog undoUnless { isSameType1(tp1, tp2) @@ -6308,14 +6305,14 @@ trait Types extends api.Types { self: SymbolTable => case List() => NothingClass.tpe case List(t) => t case _ => - incCounter(lubCount) - val start = startTimer(lubNanos) + Statistics.incCounter(lubCount) + val start = Statistics.startTimer(lubNanos) try { lub(ts, lubDepth(ts)) } finally { lubResults.clear() glbResults.clear() - stopTimer(lubNanos, start) + Statistics.stopTimer(lubNanos, start) } } @@ -6431,7 +6428,7 @@ trait Types extends api.Types { self: SymbolTable => indent = indent + " " assert(indent.length <= 100) } - incCounter(nestedLubCount) + Statistics.incCounter(nestedLubCount) val res = lub0(ts) if (printLubs) { indent = indent stripSuffix " " @@ -6456,14 +6453,14 @@ trait Types extends api.Types { self: SymbolTable => case List() => AnyClass.tpe case List(t) => t case ts0 => - incCounter(lubCount) - val start = startTimer(lubNanos) + Statistics.incCounter(lubCount) + val start = Statistics.startTimer(lubNanos) try { glbNorm(ts0, lubDepth(ts0)) } finally { lubResults.clear() glbResults.clear() - stopTimer(lubNanos, start) + Statistics.stopTimer(lubNanos, start) } } @@ -6578,7 +6575,7 @@ trait Types extends api.Types { self: SymbolTable => } // if (settings.debug.value) { println(indent + "glb of " + ts + " at depth "+depth); indent = indent + " " } //DEBUG - incCounter(nestedLubCount) + Statistics.incCounter(nestedLubCount) val res = glb0(ts) // if (settings.debug.value) { indent = indent.substring(0, indent.length() - 2); log(indent + "glb of " + ts + " is " + res) }//DEBUG @@ -6871,4 +6868,27 @@ trait Types extends api.Types { self: SymbolTable => implicit val TypeBoundsTag = ClassTag[TypeBounds](classOf[TypeBounds]) implicit val TypeRefTag = ClassTag[TypeRef](classOf[TypeRef]) implicit val TypeTagg = ClassTag[Type](classOf[Type]) + + Statistics.newView("#unique types") { if (uniques == null) 0 else uniques.size } +} + +object TypesStats { + import BaseTypeSeqsStats._ + val rawTypeCount = Statistics.newCounter ("#raw type creations") + val asSeenFromCount = Statistics.newCounter ("#asSeenFrom ops") + val subtypeCount = Statistics.newCounter ("#subtype ops") + val sametypeCount = Statistics.newCounter ("#sametype ops") + val lubCount = Statistics.newCounter ("#toplevel lubs/glbs") + val nestedLubCount = Statistics.newCounter ("#all lubs/glbs") + val findMemberCount = Statistics.newCounter ("#findMember ops") + val noMemberCount = Statistics.newSubCounter(" of which not found", findMemberCount) + val multMemberCount = Statistics.newSubCounter(" of which multiple overloaded", findMemberCount) + val typerNanos = Statistics.newTimer ("time spent typechecking", "typer") + val lubNanos = Statistics.newSubTimer ("time spent in lubs", typerNanos) + val subtypeNanos = Statistics.newSubTimer ("time spent in <:<", typerNanos) + val findMemberNanos = Statistics.newSubTimer ("time spent in findmember", typerNanos) + val asSeenFromNanos = Statistics.newSubTimer ("time spent in asSeenFrom", typerNanos) + val compoundBaseTypeSeqCount = Statistics.newSubCounter(" of which for compound types", baseTypeSeqCount) + val typerefBaseTypeSeqCount = Statistics.newSubCounter(" of which for typerefs", baseTypeSeqCount) + val singletonBaseTypeSeqCount = Statistics.newSubCounter(" of which for singletons", baseTypeSeqCount) } diff --git a/src/reflect/scala/reflect/internal/util/StatBase.scala b/src/reflect/scala/reflect/internal/util/StatBase.scala deleted file mode 100644 index b033ff98bc..0000000000 --- a/src/reflect/scala/reflect/internal/util/StatBase.scala +++ /dev/null @@ -1,97 +0,0 @@ -package scala.reflect.internal.util - -class StatBase { - - private var _enabled = false - - def enabled = _enabled - def enabled_=(cond: Boolean) = { - if (cond && !_enabled) { - val test = new Timer() - val start = System.nanoTime() - var total = 0L - for (i <- 1 to 10000) { - val time = System.nanoTime() - total += System.nanoTime() - time - } - val total2 = System.nanoTime() - start - println("Enabling statistics, measuring overhead = "+ - total/10000.0+"ns to "+total2/10000.0+"ns per timer") - _enabled = true - } - } - - def currentTime() = - if (_enabled) System.nanoTime() else 0L - - def showPercent(x: Double, base: Double) = - if (base == 0) "" else " ("+"%2.1f".format(x / base * 100)+"%)" - - def incCounter(c: Counter) { - if (_enabled) c.value += 1 - } - - def incCounter(c: Counter, delta: Int) { - if (_enabled) c.value += delta - } - - def startCounter(sc: SubCounter): IntPair = - if (_enabled) sc.start() else null - - def stopCounter(sc: SubCounter, start: IntPair) { - if (_enabled) sc.stop(start) - } - - def startTimer(tm: Timer): LongPair = - if (_enabled) tm.start() else null - - def stopTimer(tm: Timer, start: LongPair) { - if (_enabled) tm.stop(start) - } - - case class IntPair(x: Int, y: Int) - case class LongPair(x: Long, y: Long) - - class Counter { - var value: Int = 0 - override def toString = value.toString - } - - class SubCounter(c: Counter) { - var value: Int = 0 - def start(): IntPair = - if (_enabled) IntPair(value, c.value) else null - def stop(prev: IntPair) { - if (_enabled) { - val IntPair(value0, cvalue0) = prev - value = value0 + c.value - cvalue0 - } - } - override def toString = - value+showPercent(value, c.value) - } - - class Timer { - var nanos: Long = 0 - var timings = 0 - def start(): LongPair = - if (_enabled) { - timings += 1 - LongPair(nanos, System.nanoTime()) - } else null - def stop(prev: LongPair) { - if (_enabled) { - val LongPair(nanos0, start) = prev - nanos = nanos0 + System.nanoTime() - start - timings += 1 - } - } - override def toString = (timings/2)+" spans, "+nanos.toString+"ns" - } - - import Predef.Class - - class ClassCounts extends scala.collection.mutable.HashMap[Class[_], Int] { - override def default(key: Class[_]) = 0 - } -} \ No newline at end of file diff --git a/src/reflect/scala/reflect/internal/util/Statistics.scala b/src/reflect/scala/reflect/internal/util/Statistics.scala index de0830aa3a..57c9e98174 100644 --- a/src/reflect/scala/reflect/internal/util/Statistics.scala +++ b/src/reflect/scala/reflect/internal/util/Statistics.scala @@ -1,37 +1,232 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Martin Odersky - */ package scala.reflect.internal.util -class Statistics extends StatBase { - val singletonBaseTypeSeqCount = new Counter - val compoundBaseTypeSeqCount = new Counter - val typerefBaseTypeSeqCount = new Counter - val findMemberCount = new Counter - val noMemberCount = new Counter - val multMemberCount = new Counter - val findMemberNanos = new Timer - val asSeenFromCount = new Counter - val asSeenFromNanos = new Timer - val subtypeCount = new Counter - val subtypeNanos = new Timer - val sametypeCount = new Counter - val rawTypeCount = new Counter - val rawTypeFailed = new SubCounter(rawTypeCount) - val findMemberFailed = new SubCounter(findMemberCount) - val subtypeFailed = new SubCounter(subtypeCount) - val rawTypeImpl = new SubCounter(rawTypeCount) - val findMemberImpl = new SubCounter(findMemberCount) - val subtypeImpl = new SubCounter(subtypeCount) - val baseTypeSeqCount = new Counter - val baseTypeSeqLenTotal = new Counter - val typeSymbolCount = new Counter - val classSymbolCount = new Counter - val lubCount = new Counter - val nestedLubCount = new Counter - val lubNanos = new Timer -} +import collection.mutable + +object Statistics { + + /** If enabled, increment counter by one */ + @inline final def incCounter(c: Counter) { + if (_enabled && c != null) c.value += 1 + } + + /** If enabled, increment counter by given delta */ + @inline final def incCounter(c: Counter, delta: Int) { + if (_enabled && c != null) c.value += delta + } + + /** If enabled, increment counter in map `ctrs` at index `key` by one */ + @inline final def incCounter[K](ctrs: QuantMap[K, Counter], key: K) = + if (_enabled && ctrs != null) ctrs(key).value += 1 + + /** If enabled, start subcounter. While active it will track all increments of + * its base counter. + */ + @inline final def startCounter(sc: SubCounter): (Int, Int) = + if (_enabled && sc != null) sc.start() else null + + /** If enabled, stop subcounter from tracking its base counter. */ + @inline final def stopCounter(sc: SubCounter, start: (Int, Int)) { + if (_enabled && sc != null) sc.stop(start) + } + + /** If enabled, start timer */ + @inline final def startTimer(tm: Timer): (Long, Long) = + if (_enabled && tm != null) tm.start() else null + + /** If enabled, stop timer */ + @inline final def stopTimer(tm: Timer, start: (Long, Long)) { + if (_enabled && tm != null) tm.stop(start) + } + + /** If enabled, push and start a new timer in timer stack */ + @inline final def pushTimerClass(timers: ByClassTimerStack, cls: Class[_]): (Long, Long) = + if (_enabled && timers != null) timers.push(cls) else null + + /** If enabled, stop and pop timer from timer stack */ + @inline final def popTimerClass(timers: ByClassTimerStack, prev: (Long, Long)) { + if (_enabled && timers != null) timers.pop(prev) + } + + /** Create a new counter that shows as `prefix` and is active in given phases */ + def newCounter(prefix: String, phases: String*) = new Counter(prefix, phases) + + /** Create a new relative counter that shows as `prefix` and is active + * in the same phases as its base counter. Relative counters print as percentages + * of their base counters. + */ + def newRelCounter(prefix: String, ctr: Counter): Counter = new RelCounter(prefix, ctr) + + /** Create a new subcounter that shows as `prefix` and is active + * in the same phases as its base counter. Subcounters can track + * increments of their base counters and print as percentages + * of their base counters. + */ + def newSubCounter(prefix: String, ctr: Counter): SubCounter = new SubCounter(prefix, ctr) + + /** Create a new counter that shows as `prefix` and is active in given phases */ + def newTimer(prefix: String, phases: String*): Timer = new Timer(prefix, phases) + + /** Create a new subtimer that shows as `prefix` and is active + * in the same phases as its base timer. Subtimers can track + * increments of their base timers and print as percentages + * of their base timers. + */ + def newSubTimer(prefix: String, timer: Timer): Timer = new SubTimer(prefix, timer) + + /** Create a new view that shows as `prefix` and is active in given phases. + * The view always reflects the current value of `quant` as a quantity. + */ + def newView(prefix: String, phases: String*)(quant: => Any): View = new View(prefix, phases, +quant) -object Statistics extends Statistics + /** Create a new quantity map that shows as `prefix` and is active in given phases. + */ + def newQuantMap[K, V <% Ordered[V]](prefix: String, phases: String*)(initValue: => V): QuantMap[K, V] = new QuantMap(prefix, phases, initValue) + /** Same as newQuantMap, where the key type is fixed to be Class[_] */ + def newByClass[V <% Ordered[V]](prefix: String, phases: String*)(initValue: => V): QuantMap[Class[_], V] = new QuantMap(prefix, phases, initValue) + + /** Create a new timer stack map, indexed by Class[_]. */ + def newByClassTimerStack(prefix: String, underlying: Timer) = new ByClassTimerStack(prefix, underlying) + + def allQuantities: Iterable[Quantity] = + for ((q, _) <- qs if !q.isInstanceOf[SubQuantity]; + r <- q :: q.children.toList if r.prefix.nonEmpty) yield r + + private def showPercent(x: Double, base: Double) = + if (base == 0) "" else f" (${x / base * 100}%2.1f%)" + + trait Quantity { + qs += (this -> ()) + val prefix: String + val phases: Seq[String] + def showAt(phase: String) = phases.isEmpty || (phases contains phase) + def line = f"$prefix%-30s: ${this}" + val children = new mutable.ListBuffer[Quantity] + } + + trait SubQuantity extends Quantity { + protected def underlying: Quantity + underlying.children += this + } + + class Counter(val prefix: String, val phases: Seq[String]) extends Quantity with Ordered[Counter] { + var value: Int = 0 + def compare(that: Counter): Int = + if (this.value < that.value) -1 + else if (this.value > that.value) 1 + else 0 + override def toString = value.toString + } + + class View(val prefix: String, val phases: Seq[String], quant: => Any) extends Quantity { + override def toString = quant.toString + } + + private class RelCounter(prefix: String, val underlying: Counter) extends Counter(prefix, underlying.phases) with SubQuantity { + override def toString = + if (value == 0) "0" + else { + assert(underlying.value != 0, prefix+"/"+underlying.line) + f"${value.toFloat / underlying.value}%2.1f" + } + } + + class SubCounter(prefix: String, override val underlying: Counter) extends Counter(prefix, underlying.phases) with SubQuantity { + def start() = (value, underlying.value) + def stop(prev: (Int, Int)) { + val (value0, uvalue0) = prev + value = value0 + underlying.value - uvalue0 + } + override def toString = + value + showPercent(value, underlying.value) + } + + class Timer(val prefix: String, val phases: Seq[String]) extends Quantity with Ordered[Timer] { + var nanos: Long = 0 + var timings = 0 + def compare(that: Timer): Int = + if (this.nanos < that.nanos) -1 + else if (this.nanos > that.nanos) 1 + else 0 + def start() = { + (nanos, System.nanoTime()) + } + def stop(prev: (Long, Long)) { + val (nanos0, start) = prev + nanos = nanos0 + System.nanoTime() - start + timings += 1 + } + override def toString = s"$timings spans, ${nanos/1000}ms" + } + + private class SubTimer(prefix: String, override val underlying: Timer) extends Timer(prefix, underlying.phases) with SubQuantity { + override def toString: String = super.toString + showPercent(nanos, underlying.nanos) + } + + /** A mutable map quantity where missing elements are automatically inserted + * on access by executing `initValue`. + */ + class QuantMap[K, V <% Ordered[V]](val prefix: String, val phases: Seq[String], initValue: => V) + extends scala.collection.mutable.HashMap[K, V] with Quantity { + override def default(key: K) = { + val elem = initValue + this(key) = elem + elem + } + override def toString = + this.toSeq.sortWith(_._2 > _._2).map { + case (cls: Class[_], elem) => + s"${cls.toString.substring(cls.toString.lastIndexOf("$") + 1)}: $elem" + case (key, elem) => + s"$key: $elem" + }.mkString(", ") + } + + /** A mutable map quantity that takes class keys to subtimer values, relative to + * some `underlying` timer. In addition, class timers can be pushed and popped. + * Pushing the timer for a class means stopping the currently active timer. + */ + class ByClassTimerStack(prefix: String, val underlying: Timer) + extends QuantMap[Class[_], Timer](prefix, underlying.phases, new SubTimer("", underlying)) with SubQuantity { + private var elems: List[(Timer, Long)] = Nil + def push(cls: Class[_]): (Long, Long) = { + val topTimer = this(cls) + elems = (topTimer, 0L) :: elems + topTimer.start() + } + def pop(prev: (Long, Long)) = { + val (nanos0, start) = prev + val duration = System.nanoTime() - start + val (topTimer, nestedNanos) :: rest = elems + topTimer.nanos = nanos0 + duration - nestedNanos + topTimer.timings += 1 + elems = rest match { + case (outerTimer, outerNested) :: elems1 => + (outerTimer, outerNested + duration) :: elems1 + case Nil => + Nil + } + } + } + + private var _enabled = false + private val qs = new mutable.WeakHashMap[Quantity, Unit] + + def enabled = _enabled + def enabled_=(cond: Boolean) = { + if (cond && !_enabled) { + val test = new Timer("", Nil) + val start = System.nanoTime() + var total = 0L + for (i <- 1 to 10000) { + val time = System.nanoTime() + total += System.nanoTime() - time + } + val total2 = System.nanoTime() - start + println("Enabling statistics, measuring overhead = "+ + total/10000.0+"ns to "+total2/10000.0+"ns per timer") + _enabled = true + } + } +} -- cgit v1.2.3