/* $Id$ */ package ibis.io.jme; /** * A hash table that aims for speed for pairs (Object, int). This one is * specially made for (object, handle) pairs. */ public final class HandleHash extends IOProperties { private static final boolean STATS = properties.getBooleanProperty( IOProperties.s_hash_stats); private static final boolean TIMINGS = properties.getBooleanProperty( IOProperties.s_hash_timings); private static final int MIN_BUCKETS = 32; private static final boolean CACHE_HASH = false; /* * Choose this value between 50 and 200. * It determines the amount of chaining * before the hashmap is resized. Lower value means less chaining, but * larger hashtable. */ private static final int RESIZE_PERCENTAGE = properties.getIntProperty( IOProperties.s_hash_resize, 100); /** Maps handle to object. */ private Object[] dataBucket; /** Maps handle to hashcode. */ private int[] hashBucket; // if (CACHE_HASH) /** Maps handle to next with same hash value. */ private int[] nextBucket; /** Maps hash value to handle. */ private int[] map; // if (STATS) private long finds; private long rebuilds; private long collisions; private long rebuild_collisions; private int maxsize; private int mapsize; // if (TIMINGS) private Timer t_insert; private Timer t_find; private Timer t_rebuild; private Timer t_growbucket; /** Initial size of buckets. */ private int initSize; /** When to grow ... */ private int sizeThreshold; /** maximum handle+1 */ private int size; /** Number of entries. */ private int present; public HandleHash() { this(MIN_BUCKETS); } public HandleHash(int sz) { int x = 1; while (x < sz) { x <<= 1; } if (x != sz) { System.err.println("Warning: Hash table size (" + sz + ") must be a power of two. Increment to " + x); sz = x; } initSize = sz; maxsize = initSize; mapsize = initSize; init(sz); if (TIMINGS) { t_insert = Timer.createTimer(); t_find = Timer.createTimer(); t_rebuild = Timer.createTimer(); t_growbucket = Timer.createTimer(); } /* TODO: Setup a shutdown system if (STATS || TIMINGS) { Runtime.getRuntime().addShutdownHook( new Thread("HandleHash ShutdownHook") { public void run() { statistics(); } }); } */ } private void init(int sz) { sizeThreshold = (sz * RESIZE_PERCENTAGE) / 100; map = new int[sz]; nextBucket = new int[sz]; if (CACHE_HASH) { hashBucket = new int[sz]; } dataBucket = new Object[sz]; size = 1; present = 0; } /** * We know size is a power of two. Make mod cheap. */ private static final int mod(int x, int size) { return (x & (size - 1)); } static final int getHashCode(Object ref) { int h = System.identityHashCode(ref); h += ~(h << 9); h ^= (h >>> 14); h += (h << 4); h ^= (h >>> 10); return h; } public final int find(Object ref) { return find(ref, getHashCode(ref)); } public final int find(Object ref, int hashcode) { if (TIMINGS) { t_find.start(); } if (STATS) { finds++; } int h = mod(hashcode, map.length); for (int i = map[h]; i > 0; i = nextBucket[i]) { if (dataBucket[i] == ref) { if (TIMINGS) { t_find.stop(); } return i; } } if (TIMINGS) { t_find.stop(); } return 0; } /** * Rebuild if fill factor is too high. */ private final void growMap() { long saved_collisions = collisions; if (TIMINGS) { t_rebuild.start(); } map = new int[(map.length << 1)]; if (map.length > mapsize) { mapsize = map.length; } sizeThreshold = (map.length * RESIZE_PERCENTAGE) / 100; for (int i = 0; i < size; i++) { if (dataBucket[i] != null) { int h; if (CACHE_HASH) { h = hashBucket[i]; } else { h = getHashCode(dataBucket[i]); } h = mod(h, map.length); if (STATS) { if (map[h] != 0) { collisions++; } } nextBucket[i] = map[h]; map[h] = i; } } if (TIMINGS) { t_rebuild.stop(); } if (STATS) { rebuild_collisions += collisions - saved_collisions; rebuilds++; } } private void growBuckets(int sz) { if (TIMINGS) { t_growbucket.start(); } int newsize = (sz << 1) + 1; if (newsize > maxsize) { maxsize = newsize; } int[] newNextBucket = new int[newsize]; System.arraycopy(nextBucket, 0, newNextBucket, 0, sz); nextBucket = newNextBucket; Object[] newDataBucket = new Object[newsize]; System.arraycopy(dataBucket, 0, newDataBucket, 0, sz); dataBucket = newDataBucket; if (CACHE_HASH) { int[] newHashBucket = new int[newsize]; System.arraycopy(hashBucket, 0, newHashBucket, 0, sz); hashBucket = newHashBucket; } if (TIMINGS) { t_growbucket.stop(); } } /** * Insert (ref, handle) into the hash table. * * @param ref * the object that is inserted * @param handle * the (int valued) key * @param hashcode * the hashcode of ref that may be kept over calls to the hash * table * @return the handle. */ public int put(Object ref, int handle, int hashcode) { if (present >= sizeThreshold) { growMap(); } if (handle >= dataBucket.length) { growBuckets(handle); } if (TIMINGS) { t_insert.start(); } int h = mod(hashcode, map.length); dataBucket[handle] = ref; if (CACHE_HASH) { hashBucket[handle] = hashcode; } if (STATS) { if (map[h] != 0) { collisions++; } } nextBucket[handle] = map[h]; map[h] = handle; if (TIMINGS) { t_insert.stop(); } present++; if (handle >= size) { size = handle + 1; } return handle; } /** * Insert (ref, handle) into the hash table lazily. If already present, the * present handle is returned instead. * * @param ref * the object that is inserted * @param handle * the (int valued) key * @param hashcode * the hashcode of ref that may be kept over calls to the hash * table * @return the handle found. */ public final int lazyPut(Object ref, int handle, int hashcode) { int f = find(ref, hashcode); if (f != 0) { return f; } return put(ref, handle, hashcode); } public final int put(Object ref, int handle) { return put(ref, handle, getHashCode(ref)); } public final int lazyPut(Object ref, int handle) { return lazyPut(ref, handle, getHashCode(ref)); } public final void clear() { // Check if the table has grown. If not, we // can reuse the existing arrays. if (present != 0) { if (size < initSize) { for (int i = 0; i < map.length; i++) { map[i] = 0; } for (int i = 0; i < size; i++) { nextBucket[i] = 0; dataBucket[i] = null; } size = 1; present = 0; } else { init(initSize); } } } /* No finalizers allowed in JME. public final void finalize() { statistics(); } */ final void statistics() { if (STATS) { System.err.println(this + ": " + // "buckets = " + size + " mapsize " + mapsize + " maxsize " + maxsize + " finds " + finds + " rebuilds " + rebuilds + " collisions " + collisions + " (rebuild " + rebuild_collisions + ")"); } if (TIMINGS) { System.err.println(this + " insert(" + t_insert.nrTimes() + ") " + Timer.format(t_insert.totalTimeVal()) + " find(" + t_find.nrTimes() + ") " + Timer.format(t_find.totalTimeVal()) + " rebuild(" + t_rebuild.nrTimes() + ") " + Timer.format(t_rebuild.totalTimeVal()) + " growBucket(" + t_growbucket.nrTimes() + ") " + Timer.format(t_growbucket.totalTimeVal())); } } }