Skip to content

Commit 959d134

Browse files
committed
Add benchmarks to compare recursive and flat cp representations
The goal of these changes is to add possibility to: - compare an efficiency and a content of both cp implementations (ClassPathImplComparator) - examine the memory consumption by creating a lot of globals using a specified classpath (ClassPathMemoryConsumptionTester) - it can be considered as e.g. some approximation of ScalaPresentationCompilers in Scala IDE when working with many projects ClassPathMemoryConsumptionTester is placed in main (I mean not test) sources so thanks to that it has properly, out of the box configured boot classpath etc. and it's easy to use it, e.g.: scala scala.tools.nsc.ClassPathMemoryConsumptionTester -YclasspathImpl:<implementation_to_test> -cp <some_cp> -sourcepath <some_sp> -requiredInstances 50 SomeFileToCompile.scala At the end it waits for the "exit" command so there can be used some profiler like JProfiler to look how the given implementation behaves. Also flat classpath implementation is set as a default one to test it on Jenkins. This particular change must be reverted when all tests will pass because for now it's not desirable to make it permanently the default representation.
1 parent a8c43dc commit 959d134

File tree

3 files changed

+221
-1
lines changed

3 files changed

+221
-1
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2014 Contributor. All rights reserved.
3+
*/
4+
package scala.tools.nsc
5+
6+
import scala.io.StdIn.readLine
7+
8+
/**
9+
* Simple application to check out amount of memory used by chosen classpath representation.
10+
* It allows us to create many scalac-like calls based on specified parameters, where each main retains Global.
11+
* And we need additional tool (e.g. profiler) to measure memory consumption itself.
12+
*/
13+
object ClassPathMemoryConsumptionTester {
14+
15+
private class TestSettings extends Settings {
16+
val requiredInstances = IntSetting("-requiredInstances",
17+
"Determine how many times classpath should be loaded", 10, Some((1, 10000)), (_: String) => None)
18+
}
19+
20+
private class MainRetainsGlobal extends scala.tools.nsc.MainClass {
21+
var retainedGlobal: Global = _
22+
override def doCompile(compiler: Global) {
23+
retainedGlobal = compiler
24+
super.doCompile(compiler)
25+
}
26+
}
27+
28+
def main(args: Array[String]): Unit = {
29+
if (args contains "-help") usage()
30+
else doTest(args)
31+
}
32+
33+
private def doTest(args: Array[String]) = {
34+
val settings = loadSettings(args.toList)
35+
36+
val mains = (1 to settings.requiredInstances.value) map (_ => new MainRetainsGlobal)
37+
38+
// we need original settings without additional params to be able to use them later
39+
val baseArgs = argsWithoutRequiredInstances(args)
40+
41+
println(s"Loading classpath ${settings.requiredInstances.value} times")
42+
val startTime = System.currentTimeMillis()
43+
44+
mains map (_.process(baseArgs))
45+
46+
val elapsed = System.currentTimeMillis() - startTime
47+
println(s"Operation finished - elapsed $elapsed ms")
48+
println("Memory consumption can be now measured")
49+
50+
var textFromStdIn = ""
51+
while (textFromStdIn.toLowerCase != "exit")
52+
textFromStdIn = readLine("Type 'exit' to close application: ")
53+
}
54+
55+
/**
56+
* Prints usage information
57+
*/
58+
private def usage(): Unit =
59+
println( """Use classpath and sourcepath options like in the case of e.g. 'scala' command.
60+
| There's also one additional option:
61+
| -requiredInstances <int value> Determine how many times classpath should be loaded
62+
""".stripMargin.trim)
63+
64+
private def loadSettings(args: List[String]) = {
65+
val settings = new TestSettings()
66+
settings.processArguments(args, processAll = true)
67+
if (settings.classpath.isDefault)
68+
settings.classpath.value = sys.props("java.class.path")
69+
settings
70+
}
71+
72+
private def argsWithoutRequiredInstances(args: Array[String]) = {
73+
val instancesIndex = args.indexOf("-requiredInstances")
74+
if (instancesIndex == -1) args
75+
else args.dropRight(args.length - instancesIndex) ++ args.drop(instancesIndex + 2)
76+
}
77+
}

src/compiler/scala/tools/nsc/settings/ScalaSettings.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ trait ScalaSettings extends AbsScalaSettings
202202
val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overriden methods.")
203203
val etaExpandKeepsStar = BooleanSetting ("-Yeta-expand-keeps-star", "Eta-expand varargs methods to T* rather than Seq[T]. This is a temporary option to ease transition.").withDeprecationMessage(removalIn212)
204204
val inferByName = BooleanSetting ("-Yinfer-by-name", "Allow inference of by-name types. This is a temporary option to ease transition. See SI-7899.").withDeprecationMessage(removalIn212)
205-
val YclasspathImpl = ChoiceSetting ("-YclasspathImpl", "implementation", "Choose classpath scanning method.", List(ClassPathRepresentationType.Recursive, ClassPathRepresentationType.Flat), ClassPathRepresentationType.Recursive)
205+
val YclasspathImpl = ChoiceSetting ("-YclasspathImpl", "implementation", "Choose classpath scanning method.", List(ClassPathRepresentationType.Recursive, ClassPathRepresentationType.Flat), ClassPathRepresentationType.Flat)
206206
val YdisableFlatCpCaching = BooleanSetting ("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.")
207207

208208
val YvirtClasses = false // too embryonic to even expose as a -Y //BooleanSetting ("-Yvirtual-classes", "Support virtual classes")
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright (c) 2014 Contributor. All rights reserved.
3+
*/
4+
package scala.tools.nsc.util
5+
6+
import scala.reflect.io.AbstractFile
7+
import scala.tools.nsc.Settings
8+
import scala.tools.nsc.settings.ClassPathRepresentationType
9+
import scala.tools.util.PathResolverFactory
10+
11+
/**
12+
* Simple application to compare efficiency of the recursive and the flat classpath representations
13+
*/
14+
object ClassPathImplComparator {
15+
16+
private class TestSettings extends Settings {
17+
val checkClasses = PathSetting("-checkClasses", "Specify names of classes which should be found separated with ;", "")
18+
val requiredIterations = IntSetting("-requiredIterations",
19+
"Repeat tests specified number of times (to check e.g. impact of caches)", 1, Some((1, Int.MaxValue)), (_: String) => None)
20+
val cpCreationRepetitions = IntSetting("-cpCreationRepetitions",
21+
"Repeat tests specified number of times (to check e.g. impact of caches)", 1, Some((1, Int.MaxValue)), (_: String) => None)
22+
val cpLookupRepetitions = IntSetting("-cpLookupRepetitions",
23+
"Repeat tests specified number of times (to check e.g. impact of caches)", 1, Some((1, Int.MaxValue)), (_: String) => None)
24+
}
25+
26+
private class DurationStats(name: String) {
27+
private var sum = 0L
28+
private var iterations = 0
29+
30+
def noteMeasuredTime(millis: Long): Unit = {
31+
sum += millis
32+
iterations += 1
33+
}
34+
35+
def printResults(): Unit = {
36+
val avg = if (iterations == 0) 0 else sum.toDouble / iterations
37+
println(s"$name - total duration: $sum ms; iterations: $iterations; avg: $avg ms")
38+
}
39+
}
40+
41+
private lazy val defaultClassesToFind = List(
42+
"scala.collection.immutable.List",
43+
"scala.Option",
44+
"scala.Int",
45+
"scala.collection.immutable.Vector",
46+
"scala.util.hashing.MurmurHash3"
47+
)
48+
49+
private val oldCpCreationStats = new DurationStats("Old classpath - create")
50+
private val oldCpSearchingStats = new DurationStats("Old classpath - search")
51+
52+
private val flatCpCreationStats = new DurationStats("Flat classpath - create")
53+
private val flatCpSearchingStats = new DurationStats("Flat classpath - search")
54+
55+
def main(args: Array[String]): Unit = {
56+
57+
if (args contains "-help")
58+
usage()
59+
else {
60+
val oldCpSettings = loadSettings(args.toList, ClassPathRepresentationType.Recursive)
61+
val flatCpSettings = loadSettings(args.toList, ClassPathRepresentationType.Flat)
62+
63+
val classesToCheck = oldCpSettings.checkClasses.value
64+
val classesToFind =
65+
if (classesToCheck.isEmpty) defaultClassesToFind
66+
else classesToCheck.split(";").toList
67+
68+
def doTest(classPath: => ClassFileLookup[AbstractFile], cpCreationStats: DurationStats, cpSearchingStats: DurationStats,
69+
cpCreationRepetitions: Int, cpLookupRepetitions: Int)= {
70+
71+
def createClassPaths() = (1 to cpCreationRepetitions).map(_ => classPath).last
72+
def testClassLookup(cp: ClassFileLookup[AbstractFile]): Boolean = (1 to cpCreationRepetitions).foldLeft(true) {
73+
case (a, _) => a && checkExistenceOfClasses(classesToFind)(cp)
74+
}
75+
76+
val cp = withMeasuredTime("Creating classpath", createClassPaths(), cpCreationStats)
77+
val result = withMeasuredTime("Searching for specified classes", testClassLookup(cp), cpSearchingStats)
78+
println(s"The end of the test case. All expected classes found = $result \n")
79+
}
80+
81+
(1 to oldCpSettings.requiredIterations.value) foreach { iteration =>
82+
if (oldCpSettings.requiredIterations.value > 1)
83+
println(s"Iteration no $iteration")
84+
85+
println("Recursive (old) classpath representation:")
86+
doTest(PathResolverFactory.create(oldCpSettings).result, oldCpCreationStats, oldCpSearchingStats,
87+
oldCpSettings.cpCreationRepetitions.value, oldCpSettings.cpLookupRepetitions.value)
88+
89+
println("Flat classpath representation:")
90+
doTest(PathResolverFactory.create(flatCpSettings).result, flatCpCreationStats, flatCpSearchingStats,
91+
flatCpSettings.cpCreationRepetitions.value, flatCpSettings.cpLookupRepetitions.value)
92+
}
93+
94+
if (oldCpSettings.requiredIterations.value > 1) {
95+
println("\nOld classpath - summary")
96+
oldCpCreationStats.printResults()
97+
oldCpSearchingStats.printResults()
98+
99+
println("\nFlat classpath - summary")
100+
flatCpCreationStats.printResults()
101+
flatCpSearchingStats.printResults()
102+
}
103+
}
104+
}
105+
106+
/**
107+
* Prints usage information
108+
*/
109+
private def usage(): Unit =
110+
println("""Use classpath and sourcepath options like in the case of e.g. 'scala' command.
111+
| There are also two additional options:
112+
| -checkClasses <semicolon separated class names> Specify names of classes which should be found
113+
| -requiredIterations <int value> Repeat tests specified count of times (to check e.g. impact of caches)
114+
| Note: Option -YclasspathImpl will be set automatically for each case.
115+
""".stripMargin.trim)
116+
117+
private def loadSettings(args: List[String], implType: String) = {
118+
val settings = new TestSettings()
119+
settings.processArguments(args, processAll = true)
120+
settings.YclasspathImpl.value = implType
121+
if (settings.classpath.isDefault)
122+
settings.classpath.value = sys.props("java.class.path")
123+
settings
124+
}
125+
126+
private def withMeasuredTime[T](operationName: String, f: => T, durationStats: DurationStats): T = {
127+
val startTime = System.currentTimeMillis()
128+
val res = f
129+
val elapsed = System.currentTimeMillis() - startTime
130+
durationStats.noteMeasuredTime(elapsed)
131+
println(s"$operationName - elapsed $elapsed ms")
132+
res
133+
}
134+
135+
private def checkExistenceOfClasses(classesToCheck: Seq[String])(classPath: ClassFileLookup[AbstractFile]): Boolean =
136+
classesToCheck.foldLeft(true) {
137+
case (res, classToCheck) =>
138+
val found = classPath.findClass(classToCheck).isDefined
139+
if (!found)
140+
println(s"Class $classToCheck not found") // of course in this case the measured time will be affected by IO operation
141+
found
142+
}
143+
}

0 commit comments

Comments
 (0)