-
Notifications
You must be signed in to change notification settings - Fork 968
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reimplement ResolveMemos with a ridiculous hand-rolled hash table
This is intended to band-aid #330 with a hash table that sucks, but ought to be much faster to copy than the stock Java one. The stock Java hash table rehashes everything every time, while this hash table can often just copy an array. That said, I didn't benchmark it or anything, it may well also be super slow. This is more or less a troll commit to get someone to do better.
- Loading branch information
Showing
2 changed files
with
144 additions
and
8 deletions.
There are no files selected for viewing
140 changes: 140 additions & 0 deletions
140
config/src/main/java/com/typesafe/config/impl/BadMap.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package com.typesafe.config.impl; | ||
|
||
/** | ||
* A terrible Map that isn't as expensive as HashMap to copy and | ||
* add one item to. Please write something real if you see this | ||
* and get cranky. | ||
*/ | ||
final class BadMap<K,V> { | ||
final static class Entry { | ||
final int hash; | ||
final Object key; | ||
final Object value; | ||
final Entry next; | ||
|
||
Entry(int hash, Object k, Object v, Entry next) { | ||
this.hash = hash; | ||
this.key = k; | ||
this.value = v; | ||
this.next = next; | ||
} | ||
|
||
Object find(Object k) { | ||
if (key.equals(k)) | ||
return value; | ||
else if (next != null) | ||
return next.find(k); | ||
else | ||
return null; | ||
} | ||
} | ||
|
||
final int size; | ||
final Entry[] entries; | ||
|
||
private final static Entry[] emptyEntries = {}; | ||
|
||
BadMap() { | ||
this(0, (Entry[]) emptyEntries); | ||
} | ||
|
||
private BadMap(int size, Entry[] entries) { | ||
this.size = size; | ||
this.entries = entries; | ||
} | ||
|
||
BadMap<K,V> copyingPut(K k, V v) { | ||
int newSize = size + 1; | ||
Entry[] newEntries; | ||
// The "- 1" is so we pick size 2 when newSize is 1. | ||
int threshold = (newSize * 2) - 1; | ||
if (threshold > (entries.length * 2)) { | ||
// nextPrime doesn't always return a prime larger than | ||
// we passed in, so this block may not actually change | ||
// the entries size. | ||
newEntries = new Entry[nextPrime(threshold)]; | ||
} else { | ||
newEntries = new Entry[entries.length]; | ||
} | ||
|
||
if (newEntries.length == entries.length) | ||
System.arraycopy(entries, 0, newEntries, 0, entries.length); | ||
else | ||
rehash(entries, newEntries); | ||
|
||
int hash = Math.abs(k.hashCode()); | ||
store(newEntries, hash, k, v); | ||
return new BadMap<K,V>(newSize, newEntries); | ||
} | ||
|
||
private static <K,V> void store(Entry[] entries, int hash, K k, V v) { | ||
int i = hash % entries.length; | ||
Entry old = entries[i]; // old may be null | ||
entries[i] = new Entry(hash, k, v, old); | ||
} | ||
|
||
private static <K,V> void store(Entry[] entries, Entry e) { | ||
int i = e.hash % entries.length; | ||
Entry old = entries[i]; // old may be null | ||
if (old == null && e.next == null) { | ||
// share the entry since it has no "next" | ||
entries[i] = e; | ||
} else { | ||
// bah, have to copy it | ||
entries[i] = new Entry(e.hash, e.key, e.value, old); | ||
} | ||
} | ||
|
||
private static <K,V> void rehash(Entry[] src, Entry[] dest) { | ||
for (Entry old : src) { | ||
if (old != null) { | ||
if (old.next == null) { | ||
store(dest, old); | ||
} else { | ||
store(dest, old.hash, old.key, old.value); | ||
store(dest, old.next); | ||
} | ||
} | ||
} | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
V get(K k) { | ||
if (entries.length == 0) { | ||
return null; | ||
} else { | ||
int hash = Math.abs(k.hashCode()); | ||
int i = hash % entries.length; | ||
Entry e = entries[i]; | ||
if (e == null) | ||
return null; | ||
else | ||
return (V) e.find(k); | ||
} | ||
} | ||
|
||
|
||
private final static int[] primes = { | ||
/* Skip some early ones that are close together */ | ||
2, /* 3, */ 5, /* 7, */ 11, /* 13, */ 17, /* 19, */ 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, | ||
73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, | ||
179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, | ||
283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, | ||
419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, | ||
547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, | ||
661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, | ||
811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, | ||
947, 953, 967, 971, 977, 983, 991, 997, 1009, | ||
/* now we start skipping some, this is arbitrary */ | ||
2053, 3079, 4057, 7103 | ||
}; | ||
|
||
private final static int nextPrime(int i) { | ||
for (int p : primes) { | ||
if (p > i) | ||
return p; | ||
} | ||
/* oh well */ | ||
return primes[primes.length - 1]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Even sadder is the fact that not even HashMap.clone seems to do the right thing.