Skip to content

Commit 733f812

Browse files
committed
Collection tweaks
1 parent f2cadd5 commit 733f812

File tree

10 files changed

+273
-276
lines changed

10 files changed

+273
-276
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,7 @@ class ScalaSettings extends Settings.SettingGroup {
178178

179179
val YnoDecodeStacktraces: Setting[Boolean] = BooleanSetting("-Yno-decode-stacktraces", "Show raw StackOverflow stacktraces, instead of decoding them into triggering operations.")
180180

181-
val YinstrumentClosures: Setting[Boolean] = BooleanSetting("-Yinstrument-closures", "Add instrumentation code that counts closure creations.")
182-
val YinstrumentAllocations: Setting[Boolean] = BooleanSetting("-Yinstrument-allocations", "Add instrumentation code that counts allocations.")
181+
val Yinstrument: Setting[Boolean] = BooleanSetting("-Yinstrument", "Add instrumentation code that counts allocations and closure creations.")
183182

184183
/** Dottydoc specific settings */
185184
val siteRoot: Setting[String] = StringSetting(

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

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -821,25 +821,13 @@ object Contexts {
821821

822822
// Types state
823823
/** A table for hash consing unique types */
824-
private[core] val uniques: util.HashSet[Type] = new util.HashSet[Type](Config.initialUniquesCapacity) {
825-
override def hash(x: Type): Int = x.hash
826-
override def isEqual(x: Type, y: Type) = x.eql(y)
827-
}
824+
private[core] val uniques: Uniques = Uniques()
828825

829826
/** A table for hash consing unique applied types */
830-
private[dotc] val uniqueAppliedTypes: AppliedUniques = new AppliedUniques
827+
private[dotc] val uniqueAppliedTypes: AppliedUniques = AppliedUniques()
831828

832829
/** A table for hash consing unique named types */
833-
private[core] val uniqueNamedTypes: NamedTypeUniques = new NamedTypeUniques
834-
835-
private def uniqueSets = Map(
836-
"uniques" -> uniques,
837-
"uniqueAppliedTypes" -> uniqueAppliedTypes,
838-
"uniqueNamedTypes" -> uniqueNamedTypes)
839-
840-
/** A map that associates label and size of all uniques sets */
841-
def uniquesSizes: Map[String, (Int, Int, Int)] =
842-
uniqueSets.transform((_, s) => (s.size, s.accesses, s.misses))
830+
private[core] val uniqueNamedTypes: NamedTypeUniques = NamedTypeUniques()
843831

844832
var emptyTypeBounds: TypeBounds = null
845833
var emptyWildcardBounds: WildcardType = null
@@ -895,15 +883,16 @@ object Contexts {
895883

896884
private[dotc] var nameCharBuffer = new Array[Char](256)
897885

898-
def reset(): Unit = {
899-
for ((_, set) <- uniqueSets) set.clear()
886+
def reset(): Unit =
887+
uniques.clear()
888+
uniqueAppliedTypes.clear()
889+
uniqueNamedTypes.clear()
900890
emptyTypeBounds = null
901891
emptyWildcardBounds = null
902892
errorTypeMsg.clear()
903893
sources.clear()
904894
sourceNamed.clear()
905895
comparers.clear() // forces re-evaluation of top and bottom classes in TypeComparer
906-
}
907896

908897
// Test that access is single threaded
909898

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

Lines changed: 48 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -4,93 +4,71 @@ package core
44
import Types._, Contexts._, util.Stats._, Hashable._, Names._
55
import config.Config
66
import Decorators._
7-
import util.HashSet
7+
import util.{HashSet, Stats}
8+
9+
class Uniques extends HashSet[Type](Config.initialUniquesCapacity):
10+
override def hash(x: Type): Int = x.hash
11+
override def isEqual(x: Type, y: Type) = x.eql(y)
812

913
/** Defines operation `unique` for hash-consing types.
1014
* Also defines specialized hash sets for hash consing uniques of a specific type.
1115
* All sets offer a `enterIfNew` method which checks whether a type
1216
* with the given parts exists already and creates a new one if not.
1317
*/
14-
object Uniques {
18+
object Uniques:
1519

16-
private def recordCaching(tp: Type): Unit = recordCaching(tp.hash, tp.getClass)
17-
private def recordCaching(h: Int, clazz: Class[?]): Unit =
18-
if (h == NotCached) {
19-
record("uncached-types")
20-
record(s"uncached: $clazz")
21-
}
22-
else {
23-
record("cached-types")
24-
record(s"cached: $clazz")
25-
}
20+
private inline def recordCaching(tp: Type): Unit = recordCaching(tp.hash, tp.getClass)
21+
private inline def recordCaching(h: Int, clazz: Class[?]): Unit =
22+
if monitored then
23+
if h == NotCached then
24+
record("uncached-types")
25+
record(s"uncached: $clazz")
26+
else
27+
record("cached-types")
28+
record(s"cached: $clazz")
2629

27-
def unique[T <: Type](tp: T)(using Context): T = {
28-
if (monitored) recordCaching(tp)
29-
if (tp.hash == NotCached) tp
30-
else if (monitored) {
31-
val size = ctx.uniques.size
32-
val result = ctx.uniques.findEntryOrUpdate(tp).asInstanceOf[T]
33-
if (ctx.uniques.size > size) record(s"fresh unique ${tp.getClass}")
34-
result
35-
}
36-
else ctx.uniques.findEntryOrUpdate(tp).asInstanceOf[T]
37-
}
38-
/* !!! DEBUG
39-
ensuring (
40-
result => tp.toString == result.toString || {
41-
println(s"cache mismatch; tp = $tp, cached = $result")
42-
false
43-
}
44-
)
45-
*/
30+
def unique[T <: Type](tp: T)(using Context): T =
31+
recordCaching(tp)
32+
if tp.hash == NotCached then tp
33+
else ctx.uniques.put(tp).asInstanceOf[T]
4634

47-
final class NamedTypeUniques extends HashSet[NamedType](Config.initialUniquesCapacity) with Hashable {
35+
final class NamedTypeUniques extends HashSet[NamedType](Config.initialUniquesCapacity) with Hashable:
4836
override def hash(x: NamedType): Int = x.hash
4937

50-
private def findPrevious(h: Int, prefix: Type, designator: Designator): NamedType = {
51-
var e = findEntryByHash(h)
52-
while (e != null) {
53-
if ((e.prefix eq prefix) && (e.designator eq designator)) return e
54-
e = nextEntryByHash(h)
55-
}
56-
e
57-
}
58-
59-
def enterIfNew(prefix: Type, designator: Designator, isTerm: Boolean)(using Context): NamedType = {
38+
def enterIfNew(prefix: Type, designator: Designator, isTerm: Boolean)(using Context): NamedType =
6039
val h = doHash(null, designator, prefix)
61-
if (monitored) recordCaching(h, classOf[NamedType])
40+
if monitored then recordCaching(h, classOf[NamedType])
6241
def newType =
6342
if (isTerm) new CachedTermRef(prefix, designator, h)
6443
else new CachedTypeRef(prefix, designator, h)
65-
if (h == NotCached) newType
66-
else {
67-
val r = findPrevious(h, prefix, designator)
68-
if ((r ne null) && (r.isTerm == isTerm)) r else addEntryAfterScan(newType)
69-
}
70-
}
71-
}
44+
if h == NotCached then newType
45+
else
46+
Stats.record(statsItem("put"))
47+
var idx = index(h)
48+
var e = entryAt(idx)
49+
while e != null do
50+
if (e.prefix eq prefix) && (e.designator eq designator) && (e.isTerm == isTerm) then return e
51+
idx = nextIndex(idx)
52+
e = entryAt(idx)
53+
addEntryAt(idx, newType)
54+
end NamedTypeUniques
7255

73-
final class AppliedUniques extends HashSet[AppliedType](Config.initialUniquesCapacity) with Hashable {
56+
final class AppliedUniques extends HashSet[AppliedType](Config.initialUniquesCapacity) with Hashable:
7457
override def hash(x: AppliedType): Int = x.hash
7558

76-
private def findPrevious(h: Int, tycon: Type, args: List[Type]): AppliedType = {
77-
var e = findEntryByHash(h)
78-
while (e != null) {
79-
if ((e.tycon eq tycon) && e.args.eqElements(args)) return e
80-
e = nextEntryByHash(h)
81-
}
82-
e
83-
}
84-
85-
def enterIfNew(tycon: Type, args: List[Type]): AppliedType = {
59+
def enterIfNew(tycon: Type, args: List[Type]): AppliedType =
8660
val h = doHash(null, tycon, args)
8761
def newType = new CachedAppliedType(tycon, args, h)
88-
if (monitored) recordCaching(h, classOf[CachedAppliedType])
89-
if (h == NotCached) newType
90-
else {
91-
val r = findPrevious(h, tycon, args)
92-
if (r ne null) r else addEntryAfterScan(newType)
93-
}
94-
}
95-
}
96-
}
62+
if monitored then recordCaching(h, classOf[CachedAppliedType])
63+
if h == NotCached then newType
64+
else
65+
Stats.record(statsItem("put"))
66+
var idx = index(h)
67+
var e = entryAt(idx)
68+
while e != null do
69+
if (e.tycon eq tycon) && e.args.eqElements(args) then return e
70+
idx = nextIndex(idx)
71+
e = entryAt(idx)
72+
addEntryAt(idx, newType)
73+
end AppliedUniques
74+
end Uniques

compiler/src/dotty/tools/dotc/transform/Instrumentation.scala

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Names._
1515
import Constants.Constant
1616

1717

18-
/** The phase is enabled if a -Yinstrument-... option is set.
18+
/** The phase is enabled if the -Yinstrument option is set.
1919
* If enabled, it counts the number of closures or allocations for each source position.
2020
* It does this by generating a call to dotty.tools.dotc.util.Stats.doRecord.
2121
*/
@@ -25,13 +25,10 @@ class Instrumentation extends MiniPhase { thisPhase =>
2525
override def phaseName: String = "instrumentation"
2626

2727
override def isEnabled(using Context) =
28-
ctx.settings.YinstrumentClosures.value ||
29-
ctx.settings.YinstrumentAllocations.value
28+
ctx.settings.Yinstrument.value
3029

3130
private val namesOfInterest = List(
32-
"::", "+=", "toString", "newArray", "box", "toCharArray",
33-
"map", "flatMap", "filter", "withFilter", "collect", "foldLeft", "foldRight", "take",
34-
"reverse", "mapConserve", "mapconserve", "filterConserve", "zip")
31+
"::", "+=", "lookup", "put", "-=")
3532
private var namesToRecord: Set[Name] = _
3633

3734
private var consName: TermName = _

compiler/src/dotty/tools/dotc/util/GenericHashMap.scala

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ object GenericHashMap:
2323
* once the number of elements reaches the table's size.
2424
*/
2525
abstract class GenericHashMap[Key <: AnyRef, Value >: Null <: AnyRef]
26-
(protected val initialCapacity: Int = 8,
27-
protected val capacityMultiple: Int = 3) extends MutableMap[Key, Value]:
26+
(initialCapacity: Int = 8, capacityMultiple: Int = 3) extends MutableMap[Key, Value]:
2827
import GenericHashMap.DenseLimit
2928

3029
protected var used: Int = _
@@ -61,12 +60,15 @@ abstract class GenericHashMap[Key <: AnyRef, Value >: Null <: AnyRef]
6160
private def index(x: Int): Int = x & (table.length - 2)
6261

6362
private def firstIndex(key: Key) = if isDense then 0 else index(hash(key))
64-
private def nextIndex(idx: Int) = index(idx + 2)
63+
private def nextIndex(idx: Int) =
64+
Stats.record(statsItem("miss"))
65+
index(idx + 2)
6566

6667
private def keyAt(idx: Int): Key = table(idx).asInstanceOf[Key]
6768
private def valueAt(idx: Int): Value = table(idx + 1).asInstanceOf[Value]
6869

6970
def lookup(key: Key): Value =
71+
Stats.record(statsItem("lookup"))
7072
var idx = firstIndex(key)
7173
var k = keyAt(idx)
7274
while k != null do
@@ -76,6 +78,7 @@ abstract class GenericHashMap[Key <: AnyRef, Value >: Null <: AnyRef]
7678
null
7779

7880
def update(key: Key, value: Value): Unit =
81+
Stats.record(statsItem("update"))
7982
var idx = firstIndex(key)
8083
var k = keyAt(idx)
8184
while k != null do
@@ -90,6 +93,7 @@ abstract class GenericHashMap[Key <: AnyRef, Value >: Null <: AnyRef]
9093
if used > limit then growTable()
9194

9295
def remove(key: Key): Unit =
96+
Stats.record(statsItem("remove"))
9397
var idx = firstIndex(key)
9498
var k = keyAt(idx)
9599
while k != null do
@@ -110,6 +114,7 @@ abstract class GenericHashMap[Key <: AnyRef, Value >: Null <: AnyRef]
110114
k = keyAt(idx)
111115

112116
private def addOld(key: Key, value: AnyRef): Unit =
117+
Stats.record(statsItem("re-enter"))
113118
var idx = firstIndex(key)
114119
var k = keyAt(idx)
115120
while k != null do
@@ -118,12 +123,7 @@ abstract class GenericHashMap[Key <: AnyRef, Value >: Null <: AnyRef]
118123
table(idx) = key
119124
table(idx + 1) = value
120125

121-
protected def growTable(): Unit =
122-
val oldTable = table
123-
val newLength =
124-
if oldTable.length == DenseLimit then DenseLimit * 2 * roundToPower(capacityMultiple)
125-
else table.length
126-
allocate(newLength)
126+
def copyFrom(oldTable: Array[AnyRef]): Unit =
127127
if isDense then
128128
Array.copy(oldTable, 0, table, 0, oldTable.length)
129129
else
@@ -133,11 +133,32 @@ abstract class GenericHashMap[Key <: AnyRef, Value >: Null <: AnyRef]
133133
if key != null then addOld(key, oldTable(idx + 1))
134134
idx += 2
135135

136-
def iterator: Iterator[(Key, Value)] =
137-
for idx <- (0 until table.length by 2).iterator
138-
if keyAt(idx) != null
139-
yield (keyAt(idx), valueAt(idx))
136+
protected def growTable(): Unit =
137+
val oldTable = table
138+
val newLength =
139+
if oldTable.length == DenseLimit then DenseLimit * 2 * roundToPower(capacityMultiple)
140+
else table.length
141+
allocate(newLength)
142+
copyFrom(oldTable)
143+
144+
private abstract class EntryIterator[T] extends Iterator[T]:
145+
def entry(idx: Int): T
146+
private var idx = 0
147+
def hasNext =
148+
while idx < table.length && table(idx) == null do idx += 2
149+
idx < table.length
150+
def next() =
151+
require(hasNext)
152+
try entry(idx) finally idx += 2
153+
154+
def iterator: Iterator[(Key, Value)] = new EntryIterator:
155+
def entry(idx: Int) = (keyAt(idx), valueAt(idx))
140156

141157
override def toString: String =
142-
iterator.map((k, v) => s"$k -> $v").mkString("LinearTable(", ", ", ")")
158+
iterator.map((k, v) => s"$k -> $v").mkString("HashMap(", ", ", ")")
159+
160+
protected def statsItem(op: String) =
161+
val prefix = if isDense then "HashMap(dense)." else "HashMap."
162+
val suffix = getClass.getSimpleName
163+
s"$prefix$op $suffix"
143164
end GenericHashMap

compiler/src/dotty/tools/dotc/util/HashMap.scala

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,10 @@ class HashMap[Key <: AnyRef, Value >: Null <: AnyRef]
88
extends GenericHashMap[Key, Value](initialCapacity, capacityMultiple):
99
import GenericHashMap.DenseLimit
1010

11-
/** Hashcode, by default `System.identityHashCode`, but can be overriden */
1211
final def hash(x: Key): Int = x.hashCode
13-
14-
/** Equality, by default `eq`, but can be overridden */
1512
final def isEqual(x: Key, y: Key): Boolean = x.equals(y)
1613

17-
// The following methdods are duplicated from GenericHashMap
14+
// The following methods are duplicated from GenericHashMap
1815
// to avoid polymorphic dispatches
1916

2017
/** Turn hashcode `x` into a table index */
@@ -27,6 +24,7 @@ extends GenericHashMap[Key, Value](initialCapacity, capacityMultiple):
2724
private def valueAt(idx: Int): Value = table(idx + 1).asInstanceOf[Value]
2825

2926
override def lookup(key: Key): Value =
27+
Stats.record(statsItem("lookup"))
3028
var idx = firstIndex(key)
3129
var k = keyAt(idx)
3230
while k != null do
@@ -36,6 +34,7 @@ extends GenericHashMap[Key, Value](initialCapacity, capacityMultiple):
3634
null
3735

3836
override def update(key: Key, value: Value): Unit =
37+
Stats.record(statsItem("update"))
3938
var idx = firstIndex(key)
4039
var k = keyAt(idx)
4140
while k != null do
@@ -48,4 +47,24 @@ extends GenericHashMap[Key, Value](initialCapacity, capacityMultiple):
4847
table(idx + 1) = value
4948
used += 1
5049
if used > limit then growTable()
50+
51+
private def addOld(key: Key, value: AnyRef): Unit =
52+
Stats.record(statsItem("re-enter"))
53+
var idx = firstIndex(key)
54+
var k = keyAt(idx)
55+
while k != null do
56+
idx = nextIndex(idx)
57+
k = keyAt(idx)
58+
table(idx) = key
59+
table(idx + 1) = value
60+
61+
override def copyFrom(oldTable: Array[AnyRef]): Unit =
62+
if isDense then
63+
Array.copy(oldTable, 0, table, 0, oldTable.length)
64+
else
65+
var idx = 0
66+
while idx < oldTable.length do
67+
val key = oldTable(idx).asInstanceOf[Key]
68+
if key != null then addOld(key, oldTable(idx + 1))
69+
idx += 2
5170
end HashMap

0 commit comments

Comments
 (0)