Skip to content

Commit bb30c7c

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 a454a99 commit bb30c7c

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
@@ -1564,14 +1564,14 @@ object SymDenotations {
15641564
initPrivateWithin: Symbol)
15651565
extends SymDenotation(symbol, maybeOwner, name, initFlags, initInfo, initPrivateWithin) {
15661566

1567-
import util.LinearTable
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: LinearTable[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): LinearTable[Name, PreDenotation] = {
1585+
private def memberCache(using Context): HashTable[Name, PreDenotation] = {
15861586
if (myMemberCachePeriod != ctx.period) {
1587-
myMemberCache = LinearTable.empty
1587+
myMemberCache = HashTable()
15881588
myMemberCachePeriod = ctx.period
15891589
}
15901590
myMemberCache
@@ -1871,7 +1871,7 @@ object SymDenotations {
18711871
var denots: PreDenotation = memberCache.lookup(name)
18721872
if (denots == null) {
18731873
denots = computeNPMembersNamed(name)
1874-
myMemberCache = memberCache.enter(name, denots)
1874+
memberCache.enter(name, denots)
18751875
}
18761876
else if (Config.checkCacheMembersNamed) {
18771877
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)