Skip to content

Commit 21f4817

Browse files
committed
Capture contextual typer timings suitable for flamegraphs
- Time spent completing lazy types is excluded from the callsite, and instead attributed to the symbol being completed itself. - The top-most implicit search or macro expansion is included in the trace ```scala package p1 import scala.reflect.macros.blackbox._ import language.experimental._ object Macro { def impl(c: Context): c.Tree = { import c.universe._ println(rootMirror.staticClass("p1.Base").knownDirectSubclasses) q"()" } def p1_Base_knownDirectSubclasses: Unit = macro impl } ``` ```scala class Test1 { Sleep.sleep(100) def twoHundred() { Sleep.sleep(200) } def fast() { "".isEmpty } def stillFast() { new Test3 } val slowVal = Sleep.sleep(140) def slowDefSlowVal = { Sleep.sleep(140) val slowValNested = Sleep.sleep(140) } def fooCall(t: Test3) = t.foo } class Test3 extends Test2(Sleep.sleep(100)) { def foo = Sleep.sleep(300) } class Test2[T](a: T) ``` Gives: ``` ⚡ cat /tmp/output.csv <root>;java;lang;invoke 6784973 <root>;scala;Boolean 1008832 <root>;<empty>;Test3;<init> 3968070 <root>;scala;AnyVal 545566 <root>;<empty>;Test1 41991858 <root>;scala;reflect;macros 3394111 <root>;<empty>;Test1;stillFast 44014771 <root>;<empty> 3888809 <root>;<empty>;Test1;slowDefSlowVal;slowValNested;<macro>;Sleep.sleep 145958765 <root>;scala;DeprecatedPredef 1905347 <root>;scala;reflect;macros;internal 1038672 <root>;<empty>;Test1;twoHundred 2229225 <root>;<empty>;Test2;a 140655 <root>;<empty>;Test1;slowVal ;<macro>;Sleep.sleep 142835436 <root>;<empty>;<local <empty>>;<macro>;Sleep.sleep 103792019 <root>;<empty>;Test1;slowVal 488540 <root>;<empty>;Test1;twoHundred;<macro>;Sleep.sleep 204254878 <root>;<empty>;Test2;<init>;a 138838 <root>;<empty>;Sleep 5413257 <root>;java;util;stream 3461340 <root>;<empty>;Test1;fooCall;t 434245 <root>;<empty>;Test1;slowVal 517689 <root>;<empty>;Test3;<init>;<macro>;Sleep.sleep 103095814 <root>;<empty>;Test3 6626318 <root>;java;lang;CharSequence 1181685 <root>;<empty>;Test2 4495052 <root>;<empty>;Test3;foo 141093 <root>;scala;collection;immutable 6981213 <root>;<empty>;Test3;foo;<macro>;Sleep.sleep 307391221 <root>;<empty>;Test2;<init> 1416591 <root>;scala;Unit 849716 <root>;scala;reflect;macros;package 1492276 <root>;<empty>;Test1;<local Test1>;<macro>;Sleep.sleep 207086274 <root>;scala;Int 3794993 <root>;<empty>;Test1;slowDefSlowVal;<macro>;Sleep.sleep 145070640 <root>;scala;Predef 13958827 <root>;<empty>;Test1;slowDefSlowVal;slowValNested 186048 <root>;<empty>;<local <empty>>;<macro>;Sleep.sleep 106121967 <root>;<empty>;Test1;<init> 19296574 <root>;scala;LowPriorityImplicits 2508560 <root>;<empty>;Test1;fooCall 1268531 <root>;<empty>;Test1;fast 2643270 <root>;<empty>;Test1;slowDefSlowVal 156368 ``` Which renders to: https://cdn.rawgit.com/retronym/672ca835fb5513003499ce6c68831219/raw/975adfa0a4d9bda45264002bf0e0206a03e2cbd4/output.svg https://cdn.rawgit.com/retronym/01b6640a1ef5584add72f432735d540e/raw/89de5e8e32ad9a2dddf1dc3473c27546e770f8f8/output-reverse.svg
1 parent 84112e8 commit 21f4817

File tree

9 files changed

+120
-22
lines changed

9 files changed

+120
-22
lines changed

src/compiler/scala/tools/nsc/Global.scala

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ package nsc
1010
import java.io.{File, FileNotFoundException, IOException}
1111
import java.net.URL
1212
import java.nio.charset.{Charset, CharsetDecoder, IllegalCharsetNameException, UnsupportedCharsetException}
13+
1314
import scala.collection.{immutable, mutable}
1415
import io.{AbstractFile, Path, SourceReader}
1516
import reporters.Reporter
1617
import util.{ClassPath, returning}
1718
import scala.reflect.ClassTag
18-
import scala.reflect.internal.util.{BatchSourceFile, NoSourceFile, ScalaClassLoader, ScriptSourceFile, SourceFile, StatisticsStatics}
19+
import scala.reflect.internal.util.{BatchSourceFile, NoSourceFile, ScalaClassLoader, ScriptSourceFile, SourceFile, Statistics, StatisticsStatics}
1920
import scala.reflect.internal.pickling.PickleBuffer
2021
import symtab.{Flags, SymbolTable, SymbolTrackers}
2122
import symtab.classfile.Pickler
@@ -26,9 +27,11 @@ import typechecker._
2627
import transform.patmat.PatternMatching
2728
import transform._
2829
import backend.{JavaPlatform, ScalaPrimitives}
29-
import backend.jvm.{GenBCode, BackendStats}
30+
import backend.jvm.{BackendStats, GenBCode}
31+
import scala.collection.mutable.ArrayBuffer
3032
import scala.concurrent.Future
3133
import scala.language.postfixOps
34+
import scala.reflect.io.Path
3235
import scala.tools.nsc.ast.{TreeGen => AstTreeGen}
3336
import scala.tools.nsc.classpath._
3437
import scala.tools.nsc.profile.Profiler
@@ -1477,8 +1480,41 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
14771480
runCheckers()
14781481

14791482
// output collected statistics
1480-
if (settings.YstatisticsEnabled && settings.Ystatistics.contains(phase.name))
1483+
if (settings.YstatisticsEnabled && settings.Ystatistics.contains(phase.name)) {
14811484
printStatisticsFor(phase)
1485+
if (phase == typerPhase) {
1486+
import java.nio.file._
1487+
val writer = Files.newBufferedWriter(java.nio.file.Paths.get("/tmp/output.csv"))
1488+
try {
1489+
for ((key @ statistics.StatsKey(sym, macroo, implicitPt), stats) <- statistics.statsMap if sym != NoSymbol) {
1490+
val ownerChain = sym.ownerChain.toArray
1491+
var i = ownerChain.length - 1
1492+
while (i >= 0) {
1493+
writer.write(ownerChain(i).name.toString)
1494+
if (i != 0) writer.write(";")
1495+
i -= 1
1496+
}
1497+
if (macroo != NoSymbol) {
1498+
writer.write(";")
1499+
writer.write("<macro>;")
1500+
writer.write(macroo.fullName)
1501+
}
1502+
if (implicitPt != NoSymbol) {
1503+
writer.write(";")
1504+
writer.write("<implicit>;")
1505+
writer.write(implicitPt.fullName)
1506+
}
1507+
writer.write(" ")
1508+
writer.write(String.valueOf(stats.duration))
1509+
writer.write("\n")
1510+
}
1511+
} finally {
1512+
writer.close()
1513+
}
1514+
}
1515+
}
1516+
1517+
14821518

14831519
advancePhase()
14841520
}
@@ -1580,9 +1616,8 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
15801616
private val hotCounters =
15811617
List(statistics.retainedCount, statistics.retainedByType, statistics.nodeByType)
15821618
private val parserStats = {
1583-
import statistics.treeNodeCount
1584-
if (settings.YhotStatisticsEnabled) treeNodeCount :: hotCounters
1585-
else List(treeNodeCount)
1619+
if (settings.YhotStatisticsEnabled) statistics.treeNodeCount :: hotCounters
1620+
else List(statistics.treeNodeCount)
15861621
}
15871622

15881623
final def printStatisticsFor(phase: Phase) = {
@@ -1599,7 +1634,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
15991634
}
16001635
}
16011636

1602-
val quants: Iterable[statistics.Quantity] =
1637+
val quants: Iterable[Statistics#Quantity] =
16031638
if (phase.name == "parser") parserStats
16041639
else if (settings.YhotStatisticsEnabled) statistics.allQuantities
16051640
else statistics.allQuantities.filterNot(q => hotCounters.contains(q))

src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,12 @@ abstract class SymbolLoaders {
213213
})
214214
}
215215

216-
override def complete(root: Symbol) {
216+
override def complete(root: Symbol): Unit = {
217+
statistics.withContext(true, root, NoSymbol, NoSymbol) {
218+
completeInternal(root)
219+
}
220+
}
221+
private def completeInternal(root: Symbol) {
217222
try {
218223
val start = java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(System.nanoTime())
219224
val currentphase = phase
@@ -225,7 +230,7 @@ abstract class SymbolLoaders {
225230
setSource(root.companionSymbol) // module -> class, class -> module
226231
}
227232
catch {
228-
case ex @ (_: IOException | _: MissingRequirementError) =>
233+
case ex@(_: IOException | _: MissingRequirementError) =>
229234
ok = false
230235
signalError(root, ex)
231236
}

src/compiler/scala/tools/nsc/typechecker/Implicits.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ trait Implicits {
8080
* @return A search result
8181
*/
8282
def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context, saveAmbiguousDivergent: Boolean, pos: Position): SearchResult = {
83+
statistics.withContext(hasLength(context.openImplicits, 0) && hasLength(openMacros, 0), context.owner, NoSymbol, pt.typeSymbol) {
84+
inferImplicit1(tree, pt, reportAmbiguous, isView, context, saveAmbiguousDivergent, pos)
85+
}
86+
}
87+
private def inferImplicit1(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context, saveAmbiguousDivergent: Boolean, pos: Position): SearchResult = {
8388
// Note that the isInvalidConversionTarget seems to make a lot more sense right here, before all the
8489
// work is performed, than at the point where it presently exists.
8590
val shouldPrint = printTypings && !context.undetparams.isEmpty

src/compiler/scala/tools/nsc/typechecker/Macros.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,12 @@ trait Macros extends MacroRuntimes with Traces with Helpers {
754754
/** Expands a term macro used in apply role as `M(2)(3)` in `val x = M(2)(3)`.
755755
* @see DefMacroExpander
756756
*/
757-
def macroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Tree = pluginsMacroExpand(typer, expandee, mode, pt)
757+
def macroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Tree = {
758+
val addContext = hasLength(openMacros, 0) && hasLength(typer.context.openImplicits, 0)
759+
statistics.withContext(addContext, typer.context.owner, expandee.symbol, NoSymbol){
760+
pluginsMacroExpand(typer, expandee, mode, pt)
761+
}
762+
}
758763

759764
/** Default implementation of `macroExpand`.
760765
* Can be overridden by analyzer plugins (see AnalyzerPlugins.pluginsMacroExpand for more details)

src/compiler/scala/tools/nsc/typechecker/Typers.scala

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1820,7 +1820,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
18201820
}
18211821
}
18221822

1823-
def typedClassDef(cdef: ClassDef): Tree = {
1823+
def typedClassDef(cdef: ClassDef): Tree = statistics.withContext(true, cdef.symbol, NoSymbol, NoSymbol) {
18241824
val clazz = cdef.symbol
18251825
val typedMods = typedModifiers(cdef.mods)
18261826
assert(clazz != NoSymbol, cdef)
@@ -1856,7 +1856,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
18561856
.setType(NoType)
18571857
}
18581858

1859-
def typedModuleDef(mdef: ModuleDef): Tree = {
1859+
def typedModuleDef(mdef: ModuleDef): Tree = statistics.withContext(true, mdef.symbol, NoSymbol, NoSymbol) {
18601860
// initialize all constructors of the linked class: the type completer (Namer.methodSig)
18611861
// might add default getters to this object. example: "object T; class T(x: Int = 1)"
18621862
val linkedClass = companionSymbolOf(mdef.symbol, context)
@@ -2032,7 +2032,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
20322032
def typedModifiers(mods: Modifiers): Modifiers =
20332033
mods.copy(annotations = Nil) setPositions mods.positions
20342034

2035-
def typedValDef(vdef: ValDef): ValDef = {
2035+
def typedValDef(vdef: ValDef): ValDef = statistics.withContext(true, vdef.symbol, NoSymbol, NoSymbol) {
20362036
val sym = vdef.symbol
20372037
val valDefTyper = {
20382038
val maybeConstrCtx =
@@ -2258,9 +2258,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
22582258
failStruct(ddef.tpt.pos, "a user-defined value class", where = "Result type")
22592259
}
22602260

2261-
def typedDefDef(ddef: DefDef): DefDef = {
2262-
// an accessor's type completer may mutate a type inside `ddef` (`== context.unit.synthetics(ddef.symbol)`)
2263-
// concretely: it sets the setter's parameter type or the getter's return type (when derived from a valdef with empty tpt)
2261+
def typedDefDef(ddef: DefDef): DefDef = statistics.withContext(true, ddef.symbol, NoSymbol, NoSymbol) {
22642262
val meth = ddef.symbol.initialize
22652263

22662264
reenterTypeParams(ddef.tparams)
@@ -2293,9 +2291,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
22932291
var rhs1 =
22942292
if (ddef.name == nme.CONSTRUCTOR && !ddef.symbol.hasStaticFlag) { // need this to make it possible to generate static ctors
22952293
if (!meth.isPrimaryConstructor &&
2296-
(!meth.owner.isClass ||
2297-
meth.owner.isModuleClass ||
2298-
meth.owner.isAnonOrRefinementClass))
2294+
(!meth.owner.isClass ||
2295+
meth.owner.isModuleClass ||
2296+
meth.owner.isAnonOrRefinementClass))
22992297
InvalidConstructorDefError(ddef)
23002298
typed(ddef.rhs)
23012299
} else if (meth.isMacro) {
@@ -2320,7 +2318,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
23202318
rhs1 = checkDead(rhs1)
23212319

23222320
if (!isPastTyper && meth.owner.isClass &&
2323-
meth.paramss.exists(ps => ps.exists(_.hasDefault) && isRepeatedParamType(ps.last.tpe)))
2321+
meth.paramss.exists(ps => ps.exists(_.hasDefault) && isRepeatedParamType(ps.last.tpe)))
23242322
StarWithDefaultError(meth)
23252323

23262324
if (!isPastTyper) {

src/library/scala/collection/immutable/StringOps.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import mutable.StringBuilder
2727
* @define coll string
2828
*/
2929
final class StringOps(override val repr: String) extends AnyVal with StringLike[String] {
30-
3130
override protected[this] def thisCollection: WrappedString = new WrappedString(repr)
3231
override protected[this] def toCollection(repr: String): WrappedString = new WrappedString(repr)
3332

src/reflect/scala/reflect/internal/Symbols.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,13 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
12361236
def next = { val r = current; current = current.owner; r }
12371237
}
12381238

1239+
@tailrec final def hasTermOwner: Boolean = this != NoSymbol && {
1240+
val owner = this.owner
1241+
if (owner.hasPackageFlag) false
1242+
else if (owner.isTerm) true
1243+
else owner.hasTermOwner
1244+
}
1245+
12391246
/** Same as `ownerChain contains sym` but more efficient, and
12401247
* with a twist for refinement classes (see RefinementClassSymbol.)
12411248
*/

src/reflect/scala/reflect/internal/Types.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4839,4 +4839,6 @@ trait TypesStats {
48394839
val typerefBaseTypeSeqCount = newSubCounter(" of which for typerefs", baseTypeSeqCount)
48404840
val singletonBaseTypeSeqCount = newSubCounter(" of which for singletons", baseTypeSeqCount)
48414841
val typeOpsStack = newTimerStack()
4842+
val completionStack = newTimerStack()
4843+
class PerSymbolStats(stats: List[Quantity])
48424844
}

src/reflect/scala/reflect/internal/util/Statistics.scala

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,56 @@ import java.util.concurrent.atomic.{AtomicInteger, AtomicLong}
99
import scala.runtime.LongRef
1010

1111
abstract class Statistics(val symbolTable: SymbolTable, settings: MutableSettings) {
12-
1312
initFromSettings(settings)
1413

1514
def initFromSettings(currentSettings: MutableSettings): Unit = {
1615
enabled = currentSettings.YstatisticsEnabled
1716
hotEnabled = currentSettings.YhotStatisticsEnabled
1817
}
1918

19+
type Sym = SymbolTable#Symbol
20+
case class StatsKey(owner: Sym, macroo: Sym, implicitPt: Sym)
21+
22+
final class StackedStats {
23+
private var startNanos = System.nanoTime()
24+
var duration: Long = 0
25+
var running = true
26+
def stop(): Unit = {
27+
assert(running)
28+
duration += System.nanoTime() - startNanos
29+
running = false
30+
}
31+
def start(): Unit = {
32+
startNanos = System.nanoTime()
33+
running = true
34+
}
35+
}
36+
37+
private val TopKey = StatsKey(symbolTable.NoSymbol, symbolTable.NoSymbol, symbolTable.NoSymbol)
38+
private var statsStack = mutable.ArrayStack[(StatsKey, StackedStats)]((TopKey, new StackedStats))
39+
val statsMap = mutable.AnyRefMap[StatsKey, StackedStats](statsStack.head)
40+
private def currentStats: StackedStats = statsStack.top._2
41+
42+
def start(owner: Sym, macroo: Sym, implicitPt: Sym): Unit = {
43+
currentStats.stop()
44+
val key = StatsKey(owner, macroo, implicitPt)
45+
val stats = statsMap.getOrNull(key) match {
46+
case null => val x = new StackedStats; statsMap(key) = x; x
47+
case existing => existing.start(); existing
48+
}
49+
statsStack.push((key, stats))
50+
}
51+
def end(): Unit = {
52+
statsStack.pop()._2.stop()
53+
currentStats.start()
54+
}
55+
@inline final def withContext[T](condition: Boolean, owner: Sym, macroo: Sym, implicitPt: Sym)(f: => T): T = {
56+
def implicitOrMacroOnStack = statsStack.exists(x => x._1.implicitPt != symbolTable.NoSymbol || x._1.macroo != symbolTable.NoSymbol)
57+
val shouldStart = StatisticsStatics.areSomeColdStatsEnabled() && enabled && condition && !implicitOrMacroOnStack
58+
if (shouldStart) start(owner, macroo, implicitPt)
59+
try { f } finally {if (shouldStart) end()}
60+
}
61+
2062
type TimerSnapshot = (Long, Long)
2163

2264
/** If enabled, increment counter by one */

0 commit comments

Comments
 (0)