Skip to content

Commit e97ae33

Browse files
authored
Merge pull request #3801 from dotty-staging/port-phase-profiling
Fix #2909: Port per-phase profiling from scalac
2 parents 1b518b1 + 7eb4dd5 commit e97ae33

File tree

8 files changed

+757
-4
lines changed

8 files changed

+757
-4
lines changed

AUTHORS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ The majority of the dotty codebase is new code, with the exception of the compon
3434
> The lexical and syntactic analysis components were adapted from the current Scala compiler. They were originally authored by Martin Odersky,
3535
> Burak Emir, Paul Phillips, Lex Spoon, Sean McDirmid and others.
3636
37+
`dotty.tools.dotc.profile`
38+
39+
> The per-phase profiling support is taken mostly as is from [scala/scala](https://github.com/scala/scala).
40+
> The original author was Mike Skells.
41+
3742
`dotty.tools.dotc.reporting`
3843

3944
> Adapted from [scala/scala](https://github.com/scala/scala) with some heavy modifications. They were originally authored by Matthias Zenger, Martin Odersky, and others.

compiler/src/dotty/tools/dotc/Run.scala

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,26 @@ import Symbols._
88
import Phases._
99
import Types._
1010
import Scopes._
11-
import typer.{FrontEnd, Typer, ImportInfo, RefChecks}
11+
import typer.{FrontEnd, ImportInfo, RefChecks, Typer}
1212
import Decorators._
1313
import io.{AbstractFile, PlainFile}
14+
1415
import scala.io.Codec
1516
import util.{Set => _, _}
1617
import reporting.Reporter
1718
import transform.TreeChecker
1819
import rewrite.Rewrites
1920
import java.io.{BufferedWriter, OutputStreamWriter}
21+
22+
import dotty.tools.dotc.profile.Profiler
2023
import printing.XprintMode
2124
import parsing.Parsers.Parser
2225
import typer.ImplicitRunInfo
2326
import collection.mutable
2427

2528
import scala.annotation.tailrec
2629
import dotty.tools.io.VirtualFile
30+
2731
import scala.util.control.NonFatal
2832

2933
/** A compiler run. Exports various methods to compile source files */
@@ -156,11 +160,15 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
156160

157161
def runPhases(implicit ctx: Context) = {
158162
var lastPrintedTree: PrintedTree = NoPrintedTree
163+
val profiler = ctx.profiler
164+
159165
for (phase <- ctx.allPhases)
160166
if (phase.isRunnable)
161167
Stats.trackTime(s"$phase ms ") {
162168
val start = System.currentTimeMillis
169+
val profileBefore = profiler.beforePhase(phase)
163170
units = phase.runOn(units)
171+
profiler.afterPhase(phase, profileBefore)
164172
if (ctx.settings.Xprint.value.containsPhase(phase)) {
165173
for (unit <- units) {
166174
lastPrintedTree =
@@ -172,9 +180,12 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
172180
for (unit <- units)
173181
Stats.record(s"retained typed trees at end of $phase", unit.tpdTree.treeSize)
174182
}
183+
184+
profiler.finished()
175185
}
176186

177187
val runCtx = ctx.fresh
188+
runCtx.setProfiler(Profiler())
178189
ctx.phases.foreach(_.initContext(runCtx))
179190
runPhases(runCtx)
180191
if (!ctx.reporter.hasErrors) Rewrites.writeBack()

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ class ScalaSettings extends Settings.SettingGroup {
113113
val YretainTrees = BooleanSetting("-Yretain-trees", "Retain trees for top-level classes, accessible from ClassSymbol#tree")
114114
val YshowTreeIds = BooleanSetting("-Yshow-tree-ids", "Uniquely tag all tree nodes in debugging output.")
115115

116+
val YprofileEnabled = BooleanSetting("-Yprofile-enabled", "Enable profiling.")
117+
val YprofileDestination = StringSetting("-Yprofile-destination", "file", "where to send profiling output - specify a file, default is to the console.", "")
118+
//.withPostSetHook( _ => YprofileEnabled.value = true )
119+
val YprofileExternalTool = PhasesSetting("-Yprofile-external-tool", "Enable profiling for a phase using an external tool hook. Generally only useful for a single phase", "typer")
120+
//.withPostSetHook( _ => YprofileEnabled.value = true )
121+
val YprofileRunGcBetweenPhases = PhasesSetting("-Yprofile-run-gc", "Run a GC between phases - this allows heap size to be accurate at the expense of more time. Specify a list of phases, or *", "_")
122+
//.withPostSetHook( _ => YprofileEnabled.value = true )
123+
116124
/** Area-specific debug output */
117125
val YexplainLowlevel = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.")
118126
val YnoDoubleBindings = BooleanSetting("-Yno-double-bindings", "Assert no namedtype is bound twice (should be enabled only if program is error-free).")

compiler/src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import Comments._
1717
import util.Positions._
1818
import ast.Trees._
1919
import ast.untpd
20-
import util.{FreshNameCreator, SimpleIdentityMap, SourceFile, NoSource}
20+
import util.{FreshNameCreator, NoSource, SimpleIdentityMap, SourceFile}
2121
import typer.{Implicits, ImportInfo, Inliner, NamerContextOps, SearchHistory, TypeAssigner, Typer}
2222
import Implicits.ContextualImplicits
2323
import config.Settings._
@@ -27,9 +27,11 @@ import reporting.diagnostic.Message
2727
import collection.mutable
2828
import collection.immutable.BitSet
2929
import printing._
30-
import config.{Settings, ScalaSettings, Platform, JavaPlatform}
30+
import config.{JavaPlatform, Platform, ScalaSettings, Settings}
31+
3132
import language.implicitConversions
3233
import DenotTransformers.DenotTransformer
34+
import dotty.tools.dotc.profile.Profiler
3335
import util.Property.Key
3436
import util.Store
3537
import xsbti.AnalysisCallback
@@ -43,7 +45,8 @@ object Contexts {
4345
private val (freshNamesLoc, store5) = store4.newLocation[FreshNameCreator](new FreshNameCreator.Default)
4446
private val (compilationUnitLoc, store6) = store5.newLocation[CompilationUnit]()
4547
private val (runLoc, store7) = store6.newLocation[Run]()
46-
private val initialStore = store7
48+
private val (profilerLoc, store8) = store7.newLocation[Profiler]()
49+
private val initialStore = store8
4750

4851
/** A context is passed basically everywhere in dotc.
4952
* This is convenient but carries the risk of captured contexts in
@@ -196,6 +199,9 @@ object Contexts {
196199
/** The current compiler-run */
197200
def run: Run = store(runLoc)
198201

202+
/** The current compiler-run profiler */
203+
def profiler: Profiler = store(profilerLoc)
204+
199205
/** The new implicit references that are introduced by this scope */
200206
protected var implicitsCache: ContextualImplicits = null
201207
def implicits: ContextualImplicits = {
@@ -460,6 +466,7 @@ object Contexts {
460466
def setSettings(settingsState: SettingsState): this.type = updateStore(settingsStateLoc, settingsState)
461467
def setCompilationUnit(compilationUnit: CompilationUnit): this.type = updateStore(compilationUnitLoc, compilationUnit)
462468
def setRun(run: Run): this.type = updateStore(runLoc, run)
469+
def setProfiler(profiler: Profiler): this.type = updateStore(profilerLoc, profiler)
463470
def setFreshNames(freshNames: FreshNameCreator): this.type = updateStore(freshNamesLoc, freshNames)
464471

465472
def setProperty[T](key: Key[T], value: T): this.type =
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package dotty.tools.dotc.profile
2+
3+
import java.util.concurrent.ThreadPoolExecutor.AbortPolicy
4+
import java.util.concurrent._
5+
import java.util.concurrent.atomic.AtomicInteger
6+
7+
import dotty.tools.dotc.core.Phases.Phase
8+
import dotty.tools.dotc.core.Contexts.Context
9+
10+
sealed trait AsyncHelper {
11+
12+
def newUnboundedQueueFixedThreadPool
13+
(nThreads: Int,
14+
shortId: String, priority : Int = Thread.NORM_PRIORITY) : ThreadPoolExecutor
15+
def newBoundedQueueFixedThreadPool
16+
(nThreads: Int, maxQueueSize: Int, rejectHandler: RejectedExecutionHandler,
17+
shortId: String, priority : Int = Thread.NORM_PRIORITY) : ThreadPoolExecutor
18+
19+
}
20+
21+
object AsyncHelper {
22+
def apply(phase: Phase)(implicit ctx: Context): AsyncHelper = ctx.profiler match {
23+
case NoOpProfiler => new BasicAsyncHelper(phase)
24+
case r: RealProfiler => new ProfilingAsyncHelper(phase, r)
25+
}
26+
27+
private abstract class BaseAsyncHelper(phase: Phase)(implicit ctx: Context) extends AsyncHelper {
28+
val baseGroup = new ThreadGroup(s"dotc-${phase.phaseName}")
29+
private def childGroup(name: String) = new ThreadGroup(baseGroup, name)
30+
31+
protected def wrapRunnable(r: Runnable, shortId:String): Runnable
32+
33+
protected class CommonThreadFactory(shortId: String,
34+
daemon: Boolean = true,
35+
priority: Int) extends ThreadFactory {
36+
private val group: ThreadGroup = childGroup(shortId)
37+
private val threadNumber: AtomicInteger = new AtomicInteger(1)
38+
private val namePrefix = s"${baseGroup.getName}-$shortId-"
39+
40+
override def newThread(r: Runnable): Thread = {
41+
val wrapped = wrapRunnable(r, shortId)
42+
val t: Thread = new Thread(group, wrapped, namePrefix + threadNumber.getAndIncrement, 0)
43+
if (t.isDaemon != daemon) t.setDaemon(daemon)
44+
if (t.getPriority != priority) t.setPriority(priority)
45+
t
46+
}
47+
}
48+
}
49+
50+
private final class BasicAsyncHelper(phase: Phase)(implicit ctx: Context) extends BaseAsyncHelper(phase) {
51+
52+
override def newUnboundedQueueFixedThreadPool(nThreads: Int, shortId: String, priority: Int): ThreadPoolExecutor = {
53+
val threadFactory = new CommonThreadFactory(shortId, priority = priority)
54+
//like Executors.newFixedThreadPool
55+
new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue[Runnable], threadFactory)
56+
}
57+
58+
override def newBoundedQueueFixedThreadPool(nThreads: Int, maxQueueSize: Int, rejectHandler: RejectedExecutionHandler, shortId: String, priority: Int): ThreadPoolExecutor = {
59+
val threadFactory = new CommonThreadFactory(shortId, priority = priority)
60+
//like Executors.newFixedThreadPool
61+
new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue[Runnable](maxQueueSize), threadFactory, rejectHandler)
62+
}
63+
64+
override protected def wrapRunnable(r: Runnable, shortId:String): Runnable = r
65+
}
66+
67+
private class ProfilingAsyncHelper(phase: Phase, private val profiler: RealProfiler)(implicit ctx: Context) extends BaseAsyncHelper(phase) {
68+
69+
override def newUnboundedQueueFixedThreadPool(nThreads: Int, shortId: String, priority: Int): ThreadPoolExecutor = {
70+
val threadFactory = new CommonThreadFactory(shortId, priority = priority)
71+
//like Executors.newFixedThreadPool
72+
new SinglePhaseInstrumentedThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue[Runnable], threadFactory, new AbortPolicy)
73+
}
74+
75+
override def newBoundedQueueFixedThreadPool(nThreads: Int, maxQueueSize: Int, rejectHandler: RejectedExecutionHandler, shortId: String, priority: Int): ThreadPoolExecutor = {
76+
val threadFactory = new CommonThreadFactory(shortId, priority = priority)
77+
//like Executors.newFixedThreadPool
78+
new SinglePhaseInstrumentedThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue[Runnable](maxQueueSize), threadFactory, rejectHandler)
79+
}
80+
81+
override protected def wrapRunnable(r: Runnable, shortId:String): Runnable = () => {
82+
val data = new ThreadProfileData
83+
localData.set(data)
84+
85+
val profileStart = profiler.snapThread(0)
86+
try r.run finally {
87+
val snap = profiler.snapThread(data.idleNs)
88+
val threadRange = ProfileRange(profileStart, snap, phase, shortId, data.taskCount, Thread.currentThread())
89+
profiler.completeBackground(threadRange)
90+
}
91+
}
92+
93+
/**
94+
* data for thread run. Not threadsafe, only written from a single thread
95+
*/
96+
final class ThreadProfileData {
97+
var firstStartNs = 0L
98+
var taskCount = 0
99+
100+
var idleNs = 0L
101+
var runningNs = 0L
102+
103+
var lastStartNs = 0L
104+
var lastEndNs = 0L
105+
}
106+
107+
val localData = new ThreadLocal[ThreadProfileData]
108+
109+
private class SinglePhaseInstrumentedThreadPoolExecutor
110+
( corePoolSize: Int, maximumPoolSize: Int, keepAliveTime: Long, unit: TimeUnit,
111+
workQueue: BlockingQueue[Runnable], threadFactory: ThreadFactory, handler: RejectedExecutionHandler
112+
) extends ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler) {
113+
114+
override def beforeExecute(t: Thread, r: Runnable): Unit = {
115+
val data = localData.get
116+
data.taskCount += 1
117+
val now = System.nanoTime()
118+
119+
if (data.firstStartNs == 0) data.firstStartNs = now
120+
else data.idleNs += now - data.lastEndNs
121+
122+
data.lastStartNs = now
123+
124+
super.beforeExecute(t, r)
125+
}
126+
127+
override def afterExecute(r: Runnable, t: Throwable): Unit = {
128+
val now = System.nanoTime()
129+
val data = localData.get
130+
131+
data.lastEndNs = now
132+
data.runningNs += now - data.lastStartNs
133+
134+
super.afterExecute(r, t)
135+
}
136+
137+
}
138+
}
139+
}

0 commit comments

Comments
 (0)