57361
|
1 |
package jdk.jfr.internal.util;
|
|
2 |
|
|
3 |
import java.util.Arrays;
|
|
4 |
import java.util.HashMap;
|
|
5 |
import java.util.Map;
|
|
6 |
import java.util.Objects;
|
|
7 |
import java.util.function.BiConsumer;
|
|
8 |
import java.util.function.Consumer;
|
|
9 |
|
|
10 |
public class PerfectHashMap<V> {
|
|
11 |
private static final long COLLISION_SHIFT = 63;
|
|
12 |
private static final long COLLISION_BIT = 1L << COLLISION_SHIFT;
|
|
13 |
private static final long COLLISION_MASK = COLLISION_BIT - 1;
|
|
14 |
private static final int MAX_REMAP_ATTEMPTS = 100000;
|
|
15 |
private static final int MAX_ATTEMPS_BEFORE_RESIZE = 100;
|
|
16 |
|
|
17 |
static final long W = 64L;
|
|
18 |
static class LinkedValue<V> {
|
|
19 |
final V value;
|
|
20 |
long next;
|
|
21 |
|
|
22 |
LinkedValue(V value) {
|
|
23 |
this.value = value;
|
|
24 |
this.next = 0;
|
|
25 |
}
|
|
26 |
}
|
|
27 |
|
|
28 |
private UniversalHashFamily hashFamily = new UniversalHashFamily();
|
|
29 |
private PrimitiveHashMap<LinkedValue<V>> loadMap;
|
|
30 |
private Object[] valueTable;
|
|
31 |
private long[] routeTable;
|
|
32 |
private long shift;
|
|
33 |
private long shiftMask;
|
|
34 |
private int tableLengthMask;
|
|
35 |
private long primaryHashFunction = 0;
|
|
36 |
private int collisions = 0;
|
|
37 |
private int retries = 0;
|
|
38 |
private int sizeFactor = 1;
|
|
39 |
private boolean minimal;
|
|
40 |
|
|
41 |
public V get(long key) {
|
|
42 |
LinkedValue<V> v = loadMap.get(key);
|
|
43 |
return v != null ? v.value : null;
|
|
44 |
}
|
|
45 |
|
|
46 |
public V put(long key, V value) {
|
|
47 |
LinkedValue<V> existing = loadMap.put(key, new LinkedValue<V>(value));
|
|
48 |
return existing != null ? existing.value : null;
|
|
49 |
}
|
|
50 |
|
|
51 |
public void forEach(BiConsumer<? super Long, ? super V> action) {
|
|
52 |
//loadMap.forEach(PerfectHashMap<V>::callback);
|
|
53 |
}
|
|
54 |
|
|
55 |
public final void forEach(Consumer<? super V> action) {
|
|
56 |
//loadMap.forEach(action);
|
|
57 |
}
|
|
58 |
|
|
59 |
public final long[] keys() {
|
|
60 |
return loadMap.keys();
|
|
61 |
}
|
|
62 |
|
|
63 |
static class Log2 {
|
|
64 |
private static final int MAX_SIZE_EXPONENT = 32;
|
|
65 |
|
|
66 |
static long log2base10(long exponent) {
|
|
67 |
return 1L << exponent;
|
|
68 |
}
|
|
69 |
|
|
70 |
static int log2(int value) {
|
|
71 |
int i = 0;
|
|
72 |
int lastMultiple = 0;
|
|
73 |
while (i < MAX_SIZE_EXPONENT) {
|
|
74 |
int multiple = (int)log2base10(i);
|
|
75 |
if ((value & multiple) != 0) {
|
|
76 |
lastMultiple = i;
|
|
77 |
}
|
|
78 |
++i;
|
|
79 |
}
|
|
80 |
return ((int)log2base10(lastMultiple) ^ value) != 0 ? lastMultiple + 1 : lastMultiple;
|
|
81 |
}
|
|
82 |
}
|
|
83 |
|
|
84 |
static final int tableExponent(int cap) {
|
|
85 |
return Log2.log2(cap);
|
|
86 |
}
|
|
87 |
|
|
88 |
PerfectHashMap() {
|
|
89 |
this(false, 101);
|
|
90 |
}
|
|
91 |
|
|
92 |
PerfectHashMap(int size) {
|
|
93 |
this(false, size);
|
|
94 |
}
|
|
95 |
|
|
96 |
PerfectHashMap(boolean minimal, int size) {
|
|
97 |
this.minimal = minimal;
|
|
98 |
this.loadMap = new PrimitiveHashMap<>(size);
|
|
99 |
this.primaryHashFunction = hashFamily.getRandomHashFunction();
|
|
100 |
}
|
|
101 |
|
|
102 |
@SuppressWarnings("unchecked")
|
|
103 |
public V getPerfect(long key) {
|
|
104 |
int routeIdx = getIndex(key, primaryHashFunction);
|
|
105 |
assert(routeIdx >= 0);
|
|
106 |
assert(routeIdx < routeTable.length);
|
|
107 |
long element = routeTable[routeIdx];
|
|
108 |
int valueIdx = element < 0 ? getIndex(key, -element - 1) : (int)element;
|
|
109 |
assert(valueIdx >= 0);
|
|
110 |
assert(valueIdx < valueTable.length);
|
|
111 |
return (V)valueTable[valueIdx];
|
|
112 |
}
|
|
113 |
|
|
114 |
private long getRandomHashFunction() {
|
|
115 |
return hashFamily.getRandomHashFunction();
|
|
116 |
}
|
|
117 |
private int getIndex(long key, long hashFunction) {
|
|
118 |
final int idx = UniversalHashFamily.getIndex(key, hashFunction, shift, shiftMask);
|
|
119 |
assert(idx >= 0);
|
|
120 |
assert(idx < routeTable.length);
|
|
121 |
return idx;
|
|
122 |
}
|
|
123 |
private static boolean isColliding(long entry) {
|
|
124 |
return entry < 0;
|
|
125 |
}
|
|
126 |
private boolean isNonColliding(long entry) {
|
|
127 |
return entry > 0;
|
|
128 |
}
|
|
129 |
private static long setColliding(long entry) {
|
|
130 |
return entry | COLLISION_BIT;
|
|
131 |
}
|
|
132 |
private static long read(long entry) {
|
|
133 |
return entry & COLLISION_MASK;
|
|
134 |
}
|
|
135 |
|
|
136 |
private int nextValueTableSlot(int lastIdx) {
|
|
137 |
assert(lastIdx < valueTable.length);
|
|
138 |
int i = lastIdx;
|
|
139 |
for (; i < valueTable.length; ++i) {
|
|
140 |
if (valueTable[i] == null) {
|
|
141 |
break;
|
|
142 |
}
|
|
143 |
}
|
|
144 |
return i;
|
|
145 |
}
|
|
146 |
|
|
147 |
private int valueTableStore(V value, int lastIdx) {
|
|
148 |
if (lastIdx > valueTable.length) {
|
|
149 |
lastIdx = 0;
|
|
150 |
}
|
|
151 |
assert(lastIdx < valueTable.length);
|
|
152 |
final int idx = nextValueTableSlot(lastIdx);
|
|
153 |
assert(idx < valueTable.length);
|
|
154 |
assert(valueTable[idx] == null);
|
|
155 |
valueTable[idx] = value;
|
|
156 |
return idx;
|
|
157 |
}
|
|
158 |
|
|
159 |
|
|
160 |
private void routeNonCollisions() {
|
|
161 |
int lastIdx = 0;
|
|
162 |
for (int i = 0; i < routeTable.length; ++i) {
|
|
163 |
if (isNonColliding(routeTable[i])) {
|
|
164 |
lastIdx = valueTableStore(loadMap.get(routeTable[i]).value, lastIdx);
|
|
165 |
routeTable[i] = lastIdx++;
|
|
166 |
}
|
|
167 |
}
|
|
168 |
}
|
|
169 |
|
|
170 |
private void rollback(int idx, int length, long hashFunction) {
|
|
171 |
assert(isColliding(routeTable[idx]));
|
|
172 |
long key = read(routeTable[idx]);
|
|
173 |
LinkedValue<V> v = loadMap.get(key); // boxing
|
|
174 |
for (int i = 0; i < length; ++i) {
|
|
175 |
final int valueIdx = getIndex(key, hashFunction);
|
|
176 |
assert(valueIdx >= 0);
|
|
177 |
assert(valueIdx < valueTable.length);
|
|
178 |
assert(valueTable[valueIdx] != null);
|
|
179 |
valueTable[valueIdx] = null;
|
|
180 |
key = v.next;
|
|
181 |
v = loadMap.get(v.next); // no boxing
|
|
182 |
}
|
|
183 |
}
|
|
184 |
|
|
185 |
private boolean remap(int idx, long hashFunction) {
|
|
186 |
assert(isColliding(routeTable[idx]));
|
|
187 |
int completed = 0;
|
|
188 |
long key = read(routeTable[idx]);
|
|
189 |
LinkedValue<V> v = loadMap.get(key);
|
|
190 |
while (key != 0) {
|
|
191 |
final int valueIdx = getIndex(key, hashFunction);
|
|
192 |
assert(valueIdx >= 0);
|
|
193 |
assert(valueIdx < valueTable.length);
|
|
194 |
if (valueTable[valueIdx] == null) {
|
|
195 |
valueTable[valueIdx] = v.value;
|
|
196 |
++completed;
|
|
197 |
key = v.next;
|
|
198 |
v = loadMap.get(v.next);
|
|
199 |
continue;
|
|
200 |
}
|
|
201 |
rollback(idx, completed, hashFunction);
|
|
202 |
return false;
|
|
203 |
}
|
|
204 |
return true;
|
|
205 |
}
|
|
206 |
|
|
207 |
private boolean routeCollisions(int idx) {
|
|
208 |
assert(isColliding(routeTable[idx]));
|
|
209 |
boolean success = false;
|
|
210 |
int attempts = 0;
|
|
211 |
long randomHashFunction = 0;
|
|
212 |
do {
|
|
213 |
randomHashFunction = getRandomHashFunction();
|
|
214 |
success = remap(idx, randomHashFunction);
|
|
215 |
if (++attempts == MAX_REMAP_ATTEMPTS) {
|
|
216 |
System.out.println("Failed number of attempts - restart: " + attempts);
|
|
217 |
return false;
|
|
218 |
}
|
|
219 |
} while (!success);
|
|
220 |
System.out.println("Number of remap attempts: " + attempts);
|
|
221 |
routeTable[idx] = -1 - randomHashFunction;
|
|
222 |
assert(-routeTable[idx] - 1 == randomHashFunction);
|
|
223 |
return true;
|
|
224 |
}
|
|
225 |
|
|
226 |
|
|
227 |
private boolean routeCollisions() {
|
|
228 |
for (int i = 0; i < routeTable.length; ++i) {
|
|
229 |
if (isColliding(routeTable[i])) {
|
|
230 |
if (!routeCollisions(i)) {
|
|
231 |
return false;
|
|
232 |
}
|
|
233 |
}
|
|
234 |
}
|
|
235 |
return true;
|
|
236 |
}
|
|
237 |
|
|
238 |
private static void clearLongTable(long[] table) {
|
|
239 |
Arrays.fill(table, 0);
|
|
240 |
for (int i = 0; i < table.length; ++i) {
|
|
241 |
assert(table[i] == 0);
|
|
242 |
}
|
|
243 |
}
|
|
244 |
|
|
245 |
private static <T extends Object> void clearReferenceTable(T[] table) {
|
|
246 |
Arrays.fill(table, null);
|
|
247 |
for (int i = 0; i < table.length; ++i) {
|
|
248 |
assert(table[i] == null);
|
|
249 |
}
|
|
250 |
}
|
|
251 |
|
|
252 |
private void unlinkChains() {
|
|
253 |
for (long key : loadMap.keys()) {
|
|
254 |
loadMap.get(key).next = 0;
|
|
255 |
}
|
|
256 |
}
|
|
257 |
|
|
258 |
private void routeTableStore(long key, LinkedValue<V> value, int idx) {
|
|
259 |
assert(idx >= 0);
|
|
260 |
assert(idx < routeTable.length);
|
|
261 |
long existing = read(routeTable[idx]);
|
|
262 |
if (existing == 0) {
|
|
263 |
routeTable[idx] = key;
|
|
264 |
return;
|
|
265 |
}
|
|
266 |
++collisions;
|
|
267 |
routeTable[idx] = setColliding(existing);
|
|
268 |
LinkedValue<V> existingValue = loadMap.get(existing);
|
|
269 |
value.next = existingValue.next;
|
|
270 |
existingValue.next = key;
|
|
271 |
}
|
|
272 |
|
|
273 |
private void mapKeys() {
|
|
274 |
for (long key : loadMap.keys()) {
|
|
275 |
routeTableStore(key, loadMap.get(key), getIndex(key, primaryHashFunction));
|
|
276 |
}
|
|
277 |
}
|
|
278 |
|
|
279 |
private void validate() {
|
|
280 |
for (long key : loadMap.keys()) {
|
|
281 |
long element = routeTable[getIndex(key, primaryHashFunction)];
|
|
282 |
int valueIdx = element < 0 ? getIndex(key, -element - 1) : (int)element;
|
|
283 |
assert(valueIdx >= 0);
|
|
284 |
assert(loadMap.get(key) == valueTable[valueIdx]);
|
|
285 |
}
|
|
286 |
}
|
|
287 |
|
|
288 |
private void reset() {
|
|
289 |
collisions = 0;
|
|
290 |
clearLongTable(routeTable);
|
|
291 |
clearReferenceTable(valueTable);
|
|
292 |
}
|
|
293 |
|
|
294 |
private int dimensionTableSize() {
|
|
295 |
int size = loadMap.size() * sizeFactor;
|
|
296 |
return (int)Log2.log2base10(Log2.log2(size));
|
|
297 |
}
|
|
298 |
|
|
299 |
@SuppressWarnings({"rawtypes","unchecked"})
|
|
300 |
private void allocateTables() {
|
|
301 |
int size = dimensionTableSize();
|
|
302 |
this.tableLengthMask = size - 1;
|
|
303 |
this.shift = W - tableExponent(size);
|
|
304 |
this.shiftMask = Log2.log2base10(shift) - 1;
|
|
305 |
routeTable = new long[size];
|
|
306 |
valueTable = (V[])new Object[size];
|
|
307 |
collisions = 0;
|
|
308 |
retries = 0;
|
|
309 |
}
|
|
310 |
|
|
311 |
public void build() {
|
|
312 |
start:
|
|
313 |
while (true) {
|
|
314 |
allocateTables();
|
|
315 |
System.out.println("Table size " + routeTable.length);
|
|
316 |
mapKeys();
|
|
317 |
if (collisions > 0) {
|
|
318 |
if (!routeCollisions()) {
|
|
319 |
unlinkChains();
|
|
320 |
if (++retries <= MAX_ATTEMPS_BEFORE_RESIZE) {
|
|
321 |
reset();
|
|
322 |
} else {
|
|
323 |
sizeFactor *= 2;
|
|
324 |
}
|
|
325 |
continue start;
|
|
326 |
}
|
|
327 |
}
|
|
328 |
routeNonCollisions();
|
|
329 |
return;
|
|
330 |
}
|
|
331 |
}
|
|
332 |
|
|
333 |
public void rebuild() {
|
|
334 |
sizeFactor = 1;
|
|
335 |
build();
|
|
336 |
}
|
|
337 |
public int size() {
|
|
338 |
return loadMap.size();
|
|
339 |
}
|
|
340 |
} |