49
49
import com .oracle .truffle .api .profiles .InlinedConditionProfile ;
50
50
import com .oracle .truffle .api .profiles .InlinedLoopConditionProfile ;
51
51
52
- /* The Compact hash strategy from Hash Maps That Dont Hate You
53
- * See https://blog.toit.io/hash-maps-that-dont-hate-you-1a96150b492a for more details (archived at
54
- * https://archive.ph/wip/sucRY) */
52
+ /** The Compact hash strategy from Hash Maps That Don't Hate You. See
53
+ * https://blog.toit.io/hash-maps-that-dont-hate-you-1a96150b492a for more details (archived at
54
+ * https://archive.ph/wip/sucRY). Specifically that blog post has good visualizations of the index and kvStore arrays.
55
+ * We do not currently implement the deletion optimization. */
55
56
@ ExportLibrary (value = HashStoreLibrary .class )
56
57
@ GenerateUncached
57
58
public final class CompactHashStore {
@@ -77,12 +78,12 @@ public final class CompactHashStore {
77
78
// multiple not-equal hashes can map to the same position in the index array, that's a different sort of
78
79
// "collision" in addition to the usual collision of equal hash values coming from different keys. Both kinds
79
80
// of collisions actually result in the same outcome: contention on an index slot. So both are automatically
80
- // handled by the same collision-resolution strategy: open-addressing- with linear probing.)
81
+ // handled by the same collision-resolution strategy: open-addressing with linear probing.)
81
82
// -----------------------------------------------
82
83
// (3) tl;dr: Compact Hashes give you both insertion-order iteration AND the usual const-time guarantees
83
84
// ---------------------------------------------------------------------------------------------------------------
84
85
85
- /** We view the index array as consisting of "Slots ", each slot is 2 array positions. index[0] and index[1] is one
86
+ /** We view the index array as consisting of "slots ", each slot is 2 array positions. index[0] and index[1] is one
86
87
* slot, index[2] and index[3] is another,... The first position of a slot holds the hash of a key and the second
87
88
* holds an offsetted index into the KV store (index + 1) where the key is stored. The reason we store an offsetted
88
89
* index and not the index itself is so that 0 is not a valid index, thus we can use it as unused slot marker, and
@@ -98,8 +99,8 @@ public final class CompactHashStore {
98
99
* factor of 0.75 and an index of total size 64 slots, that number is 48 slots. (Technically, that's redundant
99
100
* information that is derived from the load factor and the index size, but deriving it requires a float
100
101
* multiplication or division, and we want to check for it on every insertion, so it's inefficient to keep
101
- * calculating it every time its needed) */
102
- private int numSlotsForIndexRebuild ;
102
+ * calculating it every time it is needed) */
103
+ private int indexGrowthThreshold ;
103
104
104
105
/** Each slot in the index array can be in one of 3 states, depending on the value of its second (offset) field:
105
106
* <li>Offset >= 1: Filled, the data in the hash field and the offset field is valid. Subtracting one from the
@@ -112,27 +113,12 @@ public final class CompactHashStore {
112
113
private static final int INDEX_SLOT_UNUSED = 0 ;
113
114
private static final int INDEX_SLOT_DELETED = -1 ;
114
115
115
- // returned by methods doing array search which don't find what they're looking for
116
+ // Returned by methods doing array search which don't find what they are looking for
116
117
static final int KEY_NOT_FOUND = -2 ;
117
- private static final int HASH_NOT_FOUND = KEY_NOT_FOUND ;
118
-
119
- // In hash entries, not array positions (in general, capacities and sizes are always in entries)
120
- public static final int DEFAULT_INITIAL_CAPACITY = 8 ;
118
+ static final int HASH_NOT_FOUND = KEY_NOT_FOUND ;
121
119
122
120
public static final float THRESHOLD_LOAD_FACTOR_FOR_INDEX_REBUILD = 0.75f ;
123
121
124
- public CompactHashStore () {
125
- this (DEFAULT_INITIAL_CAPACITY );
126
- }
127
-
128
- // private non-allocating constructor for .copy(), not part of the interface
129
- private CompactHashStore (int [] index , Object [] kvs , int kvStoreInsertionPos , int numSlotsForIndexRebuild ) {
130
- this .index = index ;
131
- this .kvStore = kvs ;
132
- this .kvStoreInsertionPos = kvStoreInsertionPos ;
133
- this .numSlotsForIndexRebuild = numSlotsForIndexRebuild ;
134
- }
135
-
136
122
public CompactHashStore (int capacity ) {
137
123
if (capacity < 1 || capacity >= (1 << 28 )) {
138
124
throw shouldNotReachHere ();
@@ -144,17 +130,27 @@ public CompactHashStore(int capacity) {
144
130
// This way the initial load factor (for capacity entries) is between and 0.25 and 0.5.
145
131
int indexCapacity = 2 * kvCapacity ;
146
132
147
- // All 0s by default, so all slots are marked empty for free
133
+ // All zeros by default, so all slots are marked empty for free
148
134
this .index = new int [2 * indexCapacity ];
149
135
this .kvStore = new Object [2 * kvCapacity ];
150
- this .kvStoreInsertionPos = 0 ; // always increases by 2, never decreases
151
- this .numSlotsForIndexRebuild = (int ) (indexCapacity * THRESHOLD_LOAD_FACTOR_FOR_INDEX_REBUILD );
136
+ this .kvStoreInsertionPos = 0 ;
137
+ this .indexGrowthThreshold = (int ) (indexCapacity * THRESHOLD_LOAD_FACTOR_FOR_INDEX_REBUILD );
138
+ }
139
+
140
+ // private non-allocating constructor for .copy()
141
+ private CompactHashStore (int [] index , Object [] kvStore , int kvStoreInsertionPos , int indexGrowthThreshold ) {
142
+ this .index = index ;
143
+ this .kvStore = kvStore ;
144
+ this .kvStoreInsertionPos = kvStoreInsertionPos ;
145
+ this .indexGrowthThreshold = indexGrowthThreshold ;
152
146
}
153
147
148
+ /** Rounds up powers of 2 themselves : 1 => 2, 2 => 4, 3 => 4, 4 => 8, ... */
154
149
private static int roundUpwardsToNearestPowerOf2 (int num ) {
155
- return Integer .highestOneBit (num ) << 1 ; // rounds powers of 2 themselves : 1 => 2, 2 => 4, 3 => 4, 4 => 8, ...
150
+ return Integer .highestOneBit (num ) << 1 ;
156
151
}
157
152
153
+ // For promoting from packed to compact
158
154
public void putHashKeyValue (int hashcode , Object key , Object value ) {
159
155
int pos = kvStoreInsertionPos ;
160
156
SetKvAtNode .insertIntoKv (this , key , value );
@@ -313,12 +309,12 @@ void rehash(RubyHash hash,
313
309
@ Cached @ Exclusive InlinedLoopConditionProfile loopProfile ,
314
310
@ CachedLibrary ("this" ) HashStoreLibrary hashlib ,
315
311
@ Bind ("$node" ) Node node ) {
316
- Object [] oldKvStore = kvStore ;
317
- int oldKvStoreInsertionPos = kvStoreInsertionPos ;
312
+ Object [] oldKvStore = this . kvStore ;
313
+ int oldKvStoreInsertionPos = this . kvStoreInsertionPos ;
318
314
319
315
this .kvStore = new Object [oldKvStore .length ];
320
316
this .kvStoreInsertionPos = 0 ;
321
- this .index = new int [index .length ];
317
+ this .index = new int [this . index .length ];
322
318
hash .size = 0 ;
323
319
324
320
int i = 0 ;
@@ -401,7 +397,7 @@ private int firstNonNullKeyPosFromBeginning(
401
397
}
402
398
403
399
private CompactHashStore copy () {
404
- return new CompactHashStore (index .clone (), kvStore .clone (), kvStoreInsertionPos , numSlotsForIndexRebuild );
400
+ return new CompactHashStore (index .clone (), kvStore .clone (), kvStoreInsertionPos , indexGrowthThreshold );
405
401
}
406
402
407
403
/** @param hash a hash code
@@ -567,7 +563,7 @@ static boolean keyDoesntExist(
567
563
@ Cached @ Exclusive InlinedLoopConditionProfile indexSlotUnavailable ,
568
564
@ Bind ("$node" ) Node node ) {
569
565
if (indexResizingIsNeeded .profile (node ,
570
- hash .size >= store .numSlotsForIndexRebuild )) {
566
+ hash .size >= store .indexGrowthThreshold )) {
571
567
resizeIndex (store , node );
572
568
}
573
569
if (kvResizingIsNeeded .profile (node ,
@@ -612,7 +608,7 @@ private static void resizeIndex(CompactHashStore store, Node node) {
612
608
613
609
insertIntoIndex (hash , kvPos , store .index , InlinedLoopConditionProfile .getUncached (), node );
614
610
}
615
- store .numSlotsForIndexRebuild = (int ) (oldIndex .length * THRESHOLD_LOAD_FACTOR_FOR_INDEX_REBUILD );
611
+ store .indexGrowthThreshold = (int ) (oldIndex .length * THRESHOLD_LOAD_FACTOR_FOR_INDEX_REBUILD );
616
612
}
617
613
618
614
private static void resizeKvStore (CompactHashStore store ) {
0 commit comments