Skip to content

Commit 89861f5

Browse files
committed
Use an optimized HashTable instead of a LinearTable
The two implementation classes of LinearTable had so much in common that they could be combined. Since the implementation is now fixed, we can use the usual mutable interface for a table.
1 parent 2226716 commit 89861f5

File tree

3 files changed

+144
-94
lines changed

3 files changed

+144
-94
lines changed

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1552,14 +1552,14 @@ object SymDenotations {
15521552
initPrivateWithin: Symbol)
15531553
extends SymDenotation(symbol, maybeOwner, name, initFlags, initInfo, initPrivateWithin) {
15541554

1555-
import util.LinearTable
1555+
import util.HashTable
15561556

15571557
// ----- caches -------------------------------------------------------
15581558

15591559
private var myTypeParams: List[TypeSymbol] = null
15601560
private var fullNameCache: SimpleIdentityMap[QualifiedNameKind, Name] = SimpleIdentityMap.Empty
15611561

1562-
private var myMemberCache: LinearTable[Name, PreDenotation] = null
1562+
private var myMemberCache: HashTable[Name, PreDenotation] = null
15631563
private var myMemberCachePeriod: Period = Nowhere
15641564

15651565
/** A cache from types T to baseType(T, C) */
@@ -1570,9 +1570,9 @@ object SymDenotations {
15701570
private var baseDataCache: BaseData = BaseData.None
15711571
private var memberNamesCache: MemberNames = MemberNames.None
15721572

1573-
private def memberCache(using Context): LinearTable[Name, PreDenotation] = {
1573+
private def memberCache(using Context): HashTable[Name, PreDenotation] = {
15741574
if (myMemberCachePeriod != ctx.period) {
1575-
myMemberCache = LinearTable.empty
1575+
myMemberCache = HashTable()
15761576
myMemberCachePeriod = ctx.period
15771577
}
15781578
myMemberCache
@@ -1859,7 +1859,7 @@ object SymDenotations {
18591859
var denots: PreDenotation = memberCache.lookup(name)
18601860
if (denots == null) {
18611861
denots = computeNPMembersNamed(name)
1862-
myMemberCache = memberCache.enter(name, denots)
1862+
memberCache.enter(name, denots)
18631863
}
18641864
else if (Config.checkCacheMembersNamed) {
18651865
val denots1 = computeNPMembersNamed(name)
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package dotty.tools.dotc.util
2+
3+
object HashTable:
4+
inline val MaxDense = 8
5+
6+
/** A hash table using open hashing with linear scan which is also very space efficient
7+
* at small sizes.
8+
* @param initialCapacity Indicates the initial number of slots in the hash table.
9+
* The actual number of slots is always a power of 2, so the
10+
* initial size of the table will be the smallest power of two
11+
* that is equal or greater than the given `initialCapacity`.
12+
* Minimum value is 4.
13+
* @param loadFactor The maximum fraction of used elements relative to capacity.
14+
* The hash table will be re-sized once the number of elements exceeds
15+
* the current size of the hash table multiplied by loadFactor.
16+
* However, a table of size up to MaxDense will be re-sized to only
17+
* once the number of elements reaches the table's size.
18+
*/
19+
class HashTable[Key >: Null <: AnyRef, Value >: Null <: AnyRef]
20+
(initialCapacity: Int = 8: Int, loadFactor: Float = 0.33f):
21+
import HashTable.MaxDense
22+
private var used: Int = _
23+
private var limit: Int = _
24+
private var table: Array[AnyRef] = _
25+
clear()
26+
27+
private def allocate(capacity: Int) =
28+
table = new Array[AnyRef](capacity * 2)
29+
limit = if capacity <= MaxDense then capacity - 1 else (capacity * loadFactor).toInt
30+
31+
private def roundToPower(n: Int) =
32+
if Integer.bitCount(n) == 1 then n
33+
else
34+
def recur(n: Int): Int =
35+
if n == 1 then 2
36+
else recur(n >>> 1) << 1
37+
recur(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 < MaxDense
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+
allocate(table.length)
123+
if isDense then
124+
Array.copy(oldTable, 0, table, 0, oldTable.length)
125+
else
126+
var idx = 0
127+
while idx < oldTable.length do
128+
val key = oldTable(idx).asInstanceOf[Key]
129+
if key != null then addOld(key, oldTable(idx + 1))
130+
idx += 2
131+
132+
def iterator: Iterator[(Key, Value)] =
133+
for idx <- (0 until table.length by 2).iterator
134+
if keyAt(idx) != null
135+
yield (keyAt(idx), valueAt(idx))
136+
137+
override def toString: String =
138+
iterator.map((k, v) => s"$k -> $v").mkString("LinearTable(", ", ", ")")
139+
end HashTable

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

Lines changed: 0 additions & 89 deletions
This file was deleted.

0 commit comments

Comments
 (0)