Skip to content

Commit 8ba1ea0

Browse files
authored
Merge pull request #9633 from dotty-staging/full-member-caching
Cache all memberNamed results
2 parents 2e58a66 + 53961f3 commit 8ba1ea0

File tree

2 files changed

+147
-5
lines changed

2 files changed

+147
-5
lines changed

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,14 +1564,14 @@ object SymDenotations {
15641564
initPrivateWithin: Symbol)
15651565
extends SymDenotation(symbol, maybeOwner, name, initFlags, initInfo, initPrivateWithin) {
15661566

1567-
import util.LRUCache
1567+
import util.HashTable
15681568

15691569
// ----- caches -------------------------------------------------------
15701570

15711571
private var myTypeParams: List[TypeSymbol] = null
15721572
private var fullNameCache: SimpleIdentityMap[QualifiedNameKind, Name] = SimpleIdentityMap.Empty
15731573

1574-
private var myMemberCache: LRUCache[Name, PreDenotation] = null
1574+
private var myMemberCache: HashTable[Name, PreDenotation] = null
15751575
private var myMemberCachePeriod: Period = Nowhere
15761576

15771577
/** A cache from types T to baseType(T, C) */
@@ -1582,9 +1582,9 @@ object SymDenotations {
15821582
private var baseDataCache: BaseData = BaseData.None
15831583
private var memberNamesCache: MemberNames = MemberNames.None
15841584

1585-
private def memberCache(using Context): LRUCache[Name, PreDenotation] = {
1585+
private def memberCache(using Context): HashTable[Name, PreDenotation] = {
15861586
if (myMemberCachePeriod != ctx.period) {
1587-
myMemberCache = new LRUCache
1587+
myMemberCache = HashTable()
15881588
myMemberCachePeriod = ctx.period
15891589
}
15901590
myMemberCache
@@ -1868,7 +1868,7 @@ object SymDenotations {
18681868
final def nonPrivateMembersNamed(name: Name)(using Context): PreDenotation = {
18691869
Stats.record("nonPrivateMembersNamed")
18701870
if (Config.cacheMembersNamed) {
1871-
var denots: PreDenotation = memberCache lookup name
1871+
var denots: PreDenotation = memberCache.lookup(name)
18721872
if (denots == null) {
18731873
denots = computeNPMembersNamed(name)
18741874
memberCache.enter(name, denots)
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package dotty.tools.dotc.util
2+
3+
object HashTable:
4+
/** The number of elements up to which dense packing is used.
5+
* If the number of elements reaches `DenseLimit` a hash table is used instead
6+
*/
7+
inline val DenseLimit = 8
8+
9+
/** A hash table using open hashing with linear scan which is also very space efficient
10+
* at small sizes.
11+
* @param initialCapacity Indicates the initial number of slots in the hash table.
12+
* The actual number of slots is always a power of 2, so the
13+
* initial size of the table will be the smallest power of two
14+
* that is equal or greater than the given `initialCapacity`.
15+
* Minimum value is 4.
16+
* @param capacityMultiple The minimum multiple of capacity relative to used elements.
17+
* The hash table will be re-sized once the number of elements
18+
* multiplied by capacityMultiple exceeds the current size of the hash table.
19+
* However, a table of size up to DenseLimit will be re-sized only
20+
* once the number of elements reaches the table's size.
21+
*/
22+
class HashTable[Key >: Null <: AnyRef, Value >: Null <: AnyRef]
23+
(initialCapacity: Int = 8, capacityMultiple: Int = 3):
24+
import HashTable.DenseLimit
25+
26+
private var used: Int = _
27+
private var limit: Int = _
28+
private var table: Array[AnyRef] = _
29+
clear()
30+
31+
private def allocate(capacity: Int) =
32+
table = new Array[AnyRef](capacity * 2)
33+
limit = if capacity <= DenseLimit then capacity - 1 else capacity / capacityMultiple
34+
35+
private def roundToPower(n: Int) =
36+
if Integer.bitCount(n) == 1 then n
37+
else 1 << (32 - Integer.numberOfLeadingZeros(n))
38+
39+
/** Remove all elements from this table and set back to initial configuration */
40+
def clear(): Unit =
41+
used = 0
42+
allocate(roundToPower(initialCapacity max 4))
43+
44+
/** The number of elements in the set */
45+
def size: Int = used
46+
47+
private def isDense = limit < DenseLimit
48+
49+
/** Hashcode, by default `System.identityHashCode`, but can be overriden */
50+
protected def hash(x: Key): Int = System.identityHashCode(x)
51+
52+
/** Equality, by default `eq`, but can be overridden */
53+
protected def isEqual(x: Key, y: Key): Boolean = x eq y
54+
55+
/** Turn hashcode `x` into a table index */
56+
private def index(x: Int): Int = x & (table.length - 2)
57+
58+
private def firstIndex(key: Key) = if isDense then 0 else index(hash(key))
59+
private def nextIndex(idx: Int) = index(idx + 2)
60+
61+
private def keyAt(idx: Int): Key = table(idx).asInstanceOf[Key]
62+
private def valueAt(idx: Int): Value = table(idx + 1).asInstanceOf[Value]
63+
64+
/** Find entry such that `isEqual(x, entry)`. If it exists, return it.
65+
* If not, enter `x` in set and return `x`.
66+
*/
67+
def lookup(key: Key): Value =
68+
var idx = firstIndex(key)
69+
var k = keyAt(idx)
70+
while k != null do
71+
if isEqual(k, key) then return valueAt(idx)
72+
idx = nextIndex(idx)
73+
k = keyAt(idx)
74+
null
75+
76+
def enter(key: Key, value: Value): Unit =
77+
var idx = firstIndex(key)
78+
var k = keyAt(idx)
79+
while k != null do
80+
if isEqual(k, key) then
81+
table(idx + 1) = value
82+
return
83+
idx = nextIndex(idx)
84+
k = keyAt(idx)
85+
table(idx) = key
86+
table(idx + 1) = value
87+
used += 1
88+
if used > limit then growTable()
89+
90+
def invalidate(key: Key): Unit =
91+
var idx = firstIndex(key)
92+
var k = keyAt(idx)
93+
while k != null do
94+
if isEqual(k, key) then
95+
var hole = idx
96+
if !isDense then
97+
while
98+
idx = nextIndex(idx)
99+
k = keyAt(idx)
100+
k != null && index(hash(k)) != idx
101+
do
102+
table(hole) = k
103+
table(hole + 1) = valueAt(idx)
104+
hole = idx
105+
table(hole) = null
106+
used -= 1
107+
return
108+
idx = nextIndex(idx)
109+
k = keyAt(idx)
110+
111+
private def addOld(key: Key, value: AnyRef): Unit =
112+
var idx = firstIndex(key)
113+
var k = keyAt(idx)
114+
while k != null do
115+
idx = nextIndex(idx)
116+
k = keyAt(idx)
117+
table(idx) = key
118+
table(idx + 1) = value
119+
120+
private def growTable(): Unit =
121+
val oldTable = table
122+
val newLength =
123+
if oldTable.length == DenseLimit then DenseLimit * 2 * roundToPower(capacityMultiple)
124+
else table.length
125+
allocate(newLength)
126+
if isDense then
127+
Array.copy(oldTable, 0, table, 0, oldTable.length)
128+
else
129+
var idx = 0
130+
while idx < oldTable.length do
131+
val key = oldTable(idx).asInstanceOf[Key]
132+
if key != null then addOld(key, oldTable(idx + 1))
133+
idx += 2
134+
135+
def iterator: Iterator[(Key, Value)] =
136+
for idx <- (0 until table.length by 2).iterator
137+
if keyAt(idx) != null
138+
yield (keyAt(idx), valueAt(idx))
139+
140+
override def toString: String =
141+
iterator.map((k, v) => s"$k -> $v").mkString("LinearTable(", ", ", ")")
142+
end HashTable

0 commit comments

Comments
 (0)