1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.db.procore.cluster;
\r
14 import gnu.trove.impl.PrimeFinder;
\r
16 public class LongHash {
\r
17 static final int HeaderSize = 2;
\r
18 static private final int UsedAndRealSize = -2; // Number of allocated slots.
\r
19 static private final int MaxAndFreeSize = -1; // Number of used slots.
\r
20 static private final int MinRealSize = 3;
\r
21 static private final int MinRealSizeMinusOne = MinRealSize - 1;
\r
22 private static final long FREE = 0;
\r
23 private static final long REMOVED = -1;
\r
25 static final boolean isFree(long a) {
\r
29 static final boolean isFull(long a) {
\r
33 static final boolean isRemoved(long a) {
\r
34 return REMOVED == a;
\r
37 static final long setFree() {
\r
41 static final long setFull(long a) {
\r
45 static final long setRemoved() {
\r
49 static public int getRealSize(long[] longs, int hashBase) {
\r
50 long desc = longs[hashBase + UsedAndRealSize];
\r
52 int realSize = (int)desc & 0x7FFFFFFF;
\r
53 assert (realSize > MinRealSizeMinusOne);
\r
57 // private static void setRealSize(long[] longs, int hashBase, int realSize) {
\r
58 // assert (realSize > MinRealSizeMinusOne);
\r
59 // long desc = longs[hashBase + UsedAndRealSize];
\r
60 // assert(desc < 0);
\r
61 // assert((int)desc < 0);
\r
62 // desc = (desc & 0xFFFFFFFF80000000L) | realSize;
\r
63 // longs[hashBase + UsedAndRealSize] = desc;
\r
66 private static void setUsedAndRealSize(long[] longs, int hashBase, int usedSize, int realSize) {
\r
67 assert(usedSize >= 0);
\r
68 assert(realSize > MinRealSizeMinusOne);
\r
69 int index = hashBase + UsedAndRealSize;
\r
70 long desc = longs[index];
\r
71 assert(desc <= 0); // memory is initialized to zero
\r
72 desc = (long)usedSize << 32 | realSize | 0x8000000080000000L;
\r
73 longs[index] = desc;
\r
76 public static int getUsedSize(long[] longs, int hashBase) {
\r
77 long desc = longs[hashBase + UsedAndRealSize];
\r
79 assert((int)desc < 0);
\r
80 return (int)(desc >> 32) & 0x7FFFFFFF;
\r
83 static void setUsedSize(long[] longs, int hashBase, int usedSize) {
\r
84 assert (usedSize >= 0);
\r
85 int index = hashBase + UsedAndRealSize;
\r
86 long desc = longs[index];
\r
88 assert((int)desc < 0);
\r
89 desc = (desc & 0x80000000FFFFFFFFL) | (long)usedSize << 32;
\r
90 longs[index] = desc;
\r
93 // return value after decrement
\r
94 static int decUsedSize(long[] longs, int hashBase) {
\r
95 int index = hashBase + UsedAndRealSize;
\r
96 long desc = longs[index];
\r
98 int usedSize = ((int)(desc >> 32) & 0x7FFFFFFF) - 1;
\r
99 assert(usedSize >= 0);
\r
100 desc = (desc & 0x80000000FFFFFFFFL) | (long)usedSize << 32;
\r
101 longs[index] = desc;
\r
105 // return value after increment
\r
106 static int incUsedSize(long[] longs, int hashBase) {
\r
107 int index = hashBase + UsedAndRealSize;
\r
108 long desc = longs[index];
\r
110 int usedSize = ((int)(desc >> 32) & 0x7FFFFFFF) + 1;
\r
111 assert(usedSize > 0);
\r
112 desc = (desc & 0x80000000FFFFFFFFL) | (long)usedSize << 32;
\r
113 longs[index] = desc;
\r
117 static int getFreeSize(long[] longs, int hashBase) {
\r
118 long desc = longs[hashBase + MaxAndFreeSize];
\r
120 int freeSize = (int)desc;
\r
121 assert (freeSize >= 0);
\r
125 static void setFreeSize(long[] longs, int hashBase, int freeSize) {
\r
126 assert (freeSize >= 0);
\r
127 long desc = longs[hashBase + MaxAndFreeSize];
\r
129 assert((int)desc >= 0);
\r
130 desc = (desc & 0xFFFFFFFF00000000L) | freeSize;
\r
131 longs[hashBase + MaxAndFreeSize] = desc;
\r
134 static void decFreeSize(long[] longs, int hashBase) {
\r
135 long desc = longs[hashBase + MaxAndFreeSize];
\r
137 int freeSize = (int)desc;
\r
138 assert(freeSize > 0);
\r
139 desc = (desc & 0xFFFFFFFF00000000L) | --freeSize;
\r
140 longs[hashBase + MaxAndFreeSize] = desc;
\r
143 private static void setMaxAndFreeSize(long[] longs, int hashBase, int maxSize, int freeSize) {
\r
144 assert (maxSize > 0);
\r
145 assert (freeSize >= 0);
\r
146 int index = hashBase + MaxAndFreeSize;
\r
147 long desc = longs[index];
\r
148 assert(desc >= 0); // memory is initialized to zero
\r
149 desc = ((long)maxSize << 32) | freeSize;
\r
150 longs[index] = desc;
\r
153 static int getMaxSize(long[] longs, int hashBase) {
\r
154 long desc = longs[hashBase + MaxAndFreeSize];
\r
156 assert((int)desc >= 0);
\r
157 return (int)(desc >> 32);
\r
160 static void setMaxSize(long[] longs, int hashBase, int maxSize) {
\r
161 assert (maxSize > 0);
\r
162 int index = hashBase + MaxAndFreeSize;
\r
163 long desc = longs[index];
\r
165 assert((int)desc >= 0);
\r
166 desc = (desc & 0x00000000FFFFFFFFL) | (long)maxSize << 32;
\r
167 longs[index] = desc;
\r
170 public static boolean add(AllocatorI allocator, long a) {
\r
171 long[] longs = allocator.getLongs();
\r
172 int hashBase = allocator.getHashBase();
\r
173 int index = insertionIndex(longs, hashBase, a);
\r
175 return false; // already present in set, nothing to add
\r
176 long previousState = longs[index];
\r
177 assert(LongHash.isFull(a));
\r
179 postInsertHook(longs, hashBase, LongHash.isFree(previousState), allocator);
\r
180 return true; // yes, we added something
\r
183 public static boolean remove(long[] longs, int hashBase, long a) {
\r
184 int index = LongHash.index(longs, hashBase, a);
\r
186 longs[index] = setRemoved();
\r
187 decUsedSize(longs, hashBase);
\r
193 public static boolean contains(long[] longs, int hashBase,long a) {
\r
194 return index(longs, hashBase, a) >= 0;
\r
197 public static boolean isEmpty(long[] longs, int hashBase) {
\r
198 return 0 == getUsedSize(longs, hashBase);
\r
201 public static void clear(long[] longs, int hashBase) {
\r
202 long[] set = longs;
\r
203 long free = setFree();
\r
204 int capacity = getRealSize(longs, hashBase);
\r
205 for (int i = capacity; i-- > 0;) {
\r
206 set[hashBase + i] = free;
\r
208 setUsedSize(longs, hashBase, 0);
\r
209 setFreeSize(longs, hashBase, capacity);
\r
213 * Ensure that this hashtable has sufficient capacity to hold
\r
214 * <tt>desiredCapacity<tt> <b>additional</b> elements without
\r
215 * requiring a rehash. This is a tuning method you can call
\r
216 * before doing a large insert.
\r
218 * @param desiredSize an <code>int</code> value
\r
220 public static boolean ensureSize(AllocatorI allocator, int desiredSize) {
\r
221 long[] longs = allocator.getLongs();
\r
222 int hashBase = allocator.getHashBase();
\r
223 int size = getUsedSize(longs, hashBase);
\r
224 if (desiredSize > (getMaxSize(longs, hashBase) - size)) {
\r
225 int newCapacity = ((desiredSize + size) << 1) + 1;
\r
226 rehash(longs, hashBase, PrimeFinder.nextPrime(newCapacity), allocator);
\r
233 * Compresses the hashtable to the minimum prime size (as defined by
\r
234 * PrimeFinder) that will hold all of the elements currently in the table.
\r
235 * If you have done a lot of <tt>remove</tt> operations and plan to do a
\r
236 * lot of queries or insertions or iteration, it is a good idea to invoke
\r
237 * this method. Doing so will accomplish two things:
\r
240 * <li> You'll free memory allocated to the table but no longer needed
\r
241 * because of the remove()s.</li>
\r
243 * <li> You'll get better query/insert/iterator performance because there
\r
244 * won't be any <tt>REMOVED</tt> slots to skip over when probing for
\r
245 * indices in the table.</li>
\r
248 public static void compact(AllocatorI allocator) {
\r
249 long[] longs = allocator.getLongs();
\r
250 int hashBase = allocator.getHashBase();
\r
251 // need at least one free spot for open addressing
\r
252 rehash(longs, hashBase, PrimeFinder.nextPrime((getUsedSize(longs, hashBase) << 1) + 1), allocator);
\r
255 public static int setUp(AllocatorI allocator, int initialCapacity) {
\r
256 int capacity = PrimeFinder.nextPrime(initialCapacity << 1);
\r
257 int hashBase = allocator.allocate(capacity);
\r
258 assert(hashBase == allocator.getHashBase());
\r
259 long[] longs = allocator.getLongs();
\r
260 setUsedAndRealSize(longs, hashBase, 0, capacity);
\r
261 setMaxAndFreeSize(longs, hashBase, capacity >> 1, capacity);
\r
266 * Expands the set to accomodate new values.
\r
268 * @param newCapacity
\r
269 * an <code>int</code> value
\r
271 static final void rehash(long[] oldLongs, int oldHashBase, int newCapacity,
\r
272 AllocatorI allocator) {
\r
273 assert(PrimeFinder.nextPrime(newCapacity) == newCapacity);
\r
274 int oldCapacity = getRealSize(oldLongs, oldHashBase);
\r
275 int oldSize = getUsedSize(oldLongs, oldHashBase);
\r
276 // new hash base is initialized to LongHash.freeSet()
\r
277 int newHashBase = allocator.allocate(newCapacity);
\r
278 long[] newLongs = allocator.getLongs();
\r
279 setUsedAndRealSize(newLongs, newHashBase, oldSize, newCapacity);
\r
280 setMaxAndFreeSize(newLongs, newHashBase, newCapacity>>1, newCapacity - oldSize);
\r
282 for (int i = oldCapacity + oldHashBase; i-- > oldHashBase;) {
\r
283 long o = oldLongs[i];
\r
285 int index = insertionIndex(newLongs, newHashBase, o);
\r
286 newLongs[index] = o;
\r
292 * After an insert, this hook is called to adjust the size/free values of
\r
293 * the set and to perform rehashing if necessary.
\r
295 protected static final void postInsertHook(long[] longs, int hashBase,
\r
296 boolean usedFreeSlot, AllocatorI allocator) {
\r
297 if (usedFreeSlot) {
\r
298 decFreeSize(longs, hashBase);
\r
301 // rehash whenever we exhaust the available space in the table
\r
302 if (incUsedSize(longs, hashBase) > getMaxSize(longs, hashBase)
\r
303 || getFreeSize(longs, hashBase) == 0) {
\r
304 // choose a new capacity suited to the new state of the table
\r
305 // if we've grown beyond our maximum size, double capacity;
\r
306 // if we've exhausted the free spots, rehash to the same capacity,
\r
307 // which will free up any stale removed slots for reuse.
\r
308 int newCapacity = getUsedSize(longs, hashBase) > getMaxSize(longs,
\r
309 hashBase) ? PrimeFinder.nextPrime(getRealSize(longs,
\r
310 hashBase) << 1) : getRealSize(longs, hashBase);
\r
311 rehash(longs, hashBase, newCapacity, allocator);
\r
316 * Locates the index of <tt>val</tt>.
\r
319 * an <code>long</code> value
\r
320 * @return the index of <tt>val</tt> or -1 if it isn't in the set.
\r
322 static int index(long[] longs, int hashBase, long a) {
\r
323 int hash, probe, index, length, hashIndex;
\r
324 long[] set = longs;
\r
325 length = getRealSize(longs, hashBase);
\r
326 hash = computeHashCode(a);
\r
327 index = hash % length;
\r
328 hashIndex = hashBase + index;
\r
330 if (!LongHash.isFree(set[hashIndex])
\r
331 && (LongHash.isRemoved(set[hashIndex]) || set[hashIndex] != a)) {
\r
332 // see Knuth, p. 529
\r
333 probe = 1 + (hash % (length - 2));
\r
340 hashIndex = hashBase + index;
\r
341 } while (!LongHash.isFree(set[hashIndex])
\r
342 && (LongHash.isRemoved(set[hashIndex]) || set[hashIndex] != a));
\r
345 return LongHash.isFree(set[hashIndex]) ? -1 : hashIndex;
\r
349 * Locates the index at which <tt>val</tt> can be inserted. if there is
\r
350 * already a value equal()ing <tt>val</tt> in the set, returns that value
\r
351 * as a negative integer.
\r
354 * an <code>long</code> value
\r
355 * @return an <code>int</code> value
\r
357 static final int insertionIndex(long[] longs, int hashBase, long a) {
\r
358 int hash, probe, index, length, hashIndex;
\r
359 long[] set = longs;
\r
360 length = getRealSize(longs, hashBase);
\r
361 hash = computeHashCode(a);
\r
362 index = hash % length;
\r
363 assert(0 != hashBase);
\r
364 hashIndex = hashBase + index;
\r
366 if (LongHash.isFree(set[hashIndex])) {
\r
367 return hashIndex; // empty, all done
\r
368 } else if (LongHash.isFull(set[hashIndex]) && set[hashIndex] == a) {
\r
369 return -hashIndex; // already stored
\r
370 } else { // already FULL or REMOVED, must probe
\r
371 // compute the double hash
\r
372 probe = 1 + (hash % (length - 2));
\r
374 // if the slot we landed on is FULL (but not removed), probe
\r
375 // until we find an empty slot, a REMOVED slot, or an element
\r
376 // equal to the one we are trying to insert.
\r
377 // finding an empty slot means that the value is not present
\r
378 // and that we should use that slot as the insertion point;
\r
379 // finding a REMOVED slot means that we need to keep searching,
\r
380 // however we want to remember the offset of that REMOVED slot
\r
381 // so we can reuse it in case a "new" insertion (i.e. not an update)
\r
383 // finding a matching value means that we've found that our desired
\r
384 // key is already in the table
\r
386 if (!LongHash.isRemoved(set[hashIndex])) {
\r
387 // starting at the natural offset, probe until we find an
\r
388 // offset that isn't full.
\r
394 hashIndex = hashBase + index;
\r
395 } while (LongHash.isFull(set[hashIndex]) && set[hashIndex] != a);
\r
398 // if the index we found was removed: continue probing until we
\r
399 // locate a free location or an element which equal()s the
\r
401 if (LongHash.isRemoved(set[hashIndex])) {
\r
402 int firstRemoved = hashIndex;
\r
403 while (!LongHash.isFree(set[hashIndex])
\r
404 && (LongHash.isRemoved(set[hashIndex]) || set[hashIndex] != a)) {
\r
409 hashIndex = hashBase + index;
\r
411 return LongHash.isFull(set[hashIndex]) ? -hashIndex : firstRemoved;
\r
413 // if it's full, the key is already stored
\r
414 return LongHash.isFull(set[hashIndex]) ? -hashIndex : hashIndex;
\r
418 static final int computeHashCode(long aKey) {
\r
419 int hash = ((int) (aKey ^ (aKey >> 32))) * 31;
\r
420 return hash & 0x7fffffff; // Top bit reserved.
\r
423 interface AllocatorI {
\r
424 int allocate(int size); // return base address of hash table, allocates also hash header
\r
425 long[] getLongs(); // return base address of allocator memory
\r
426 int getHashBase(); // return base address of hash table
\r
430 class LongIterator {
\r
431 private long[] longs;
\r
432 private int hashBase;
\r
435 private int size; // number of elements in the set
\r
436 private final LongHash.AllocatorI allocator;
\r
438 public LongIterator(LongHash.AllocatorI allocator) {
\r
439 this.allocator = allocator;
\r
442 public int size() {
\r
445 public void reset() {
\r
446 if (longs == null ||
\r
447 LongHash.getUsedSize(longs, hashBase) != this.size ||
\r
448 longs != allocator.getLongs() ||
\r
449 hashBase != allocator.getHashBase()) {
\r
450 this.longs = allocator.getLongs();
\r
451 this.hashBase = allocator.getHashBase();
\r
452 this.size = LongHash.getUsedSize(longs, hashBase);
\r
454 this.index = LongHash.getRealSize(longs, hashBase);
\r
456 public long next() {
\r
457 if (moveToNextIndex())
\r
458 return longs[hashBase + index];
\r
459 return LongHash.setFree();
\r
461 protected final boolean moveToNextIndex() {
\r
462 // doing the assignment && < 0 in one line saves 3 opcodes...
\r
463 if ((index = nextIndex()) < 0) {
\r
468 protected final int nextIndex() {
\r
469 long[] states = longs;
\r
471 while (i-- > 0 && (!LongHash.isFull(states[hashBase + i])))
\r
475 public boolean hasNext() {
\r
476 return nextIndex() >= 0;
\r