-
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.
Merge pull request #578 from sam-token/bad-map
ResolveMemos Performance Improvement
- Loading branch information
Showing
3 changed files
with
235 additions
and
11 deletions.
There are no files selected for viewing
137 changes: 137 additions & 0 deletions
137
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,137 @@ | ||
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; | ||
} | ||
} | ||
|
||
private final int size; | ||
private final Entry[] entries; | ||
|
||
private final static Entry[] emptyEntries = {}; | ||
|
||
BadMap() { | ||
this(0, 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; | ||
if (newSize > entries.length) { | ||
// nextPrime doesn't always return a prime larger than | ||
// we passed in, so this block may not actually change | ||
// the entries size. the "-1" is to ensure we use | ||
// array length 2 when going from 0 to 1. | ||
newEntries = new Entry[nextPrime((newSize * 2) - 1)]; | ||
} 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<>(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 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 void rehash(Entry[] src, Entry[] dest) { | ||
for (Entry entry : src) { | ||
// have to store each "next" element individually; they may belong in different indices | ||
while (entry != null) { | ||
store(dest, entry); | ||
entry = entry.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, 10949, 16069, 32609, 65867, 104729 | ||
}; | ||
|
||
private 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
94 changes: 94 additions & 0 deletions
94
config/src/test/scala/com/typesafe/config/impl/BadMapTest.scala
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,94 @@ | ||
package com.typesafe.config.impl | ||
|
||
import org.junit.Assert._ | ||
import org.junit.Test | ||
|
||
class BadMapTest extends TestUtils { | ||
@Test | ||
def copyingPut(): Unit = { | ||
val map = new BadMap[String, String]() | ||
val copy = map.copyingPut("key", "value") | ||
|
||
assertNull(map.get("key")) | ||
assertEquals("value", copy.get("key")) | ||
} | ||
|
||
@Test | ||
def retrieveOldElement(): Unit = { | ||
val map = new BadMap[String, String]() | ||
.copyingPut("key1", "value1") | ||
.copyingPut("key2", "value2") | ||
.copyingPut("key3", "value3") | ||
|
||
assertEquals("value1", map.get("key1")) | ||
assertEquals("value2", map.get("key2")) | ||
assertEquals("value3", map.get("key3")) | ||
} | ||
|
||
@Test | ||
def putOverride(): Unit = { | ||
val map = new BadMap[String, String]() | ||
.copyingPut("key", "value1") | ||
.copyingPut("key", "value2") | ||
.copyingPut("key", "value3") | ||
|
||
assertEquals("value3", map.get("key")) | ||
} | ||
|
||
@Test | ||
def notFound(): Unit = { | ||
val map = new BadMap[String, String]() | ||
|
||
assertNull(map.get("invalid key")) | ||
} | ||
|
||
@Test | ||
def putMany(): Unit = { | ||
val entries = (1 to 1000).map(i => (s"key$i", s"value$i")) | ||
var map = new BadMap[String, String]() | ||
|
||
for ((key, value) <- entries) { | ||
map = map.copyingPut(key, value) | ||
} | ||
|
||
for ((key, value) <- entries) { | ||
assertEquals(value, map.get(key)) | ||
} | ||
} | ||
|
||
@Test | ||
def putSameHash(): Unit = { | ||
val hash = 2 | ||
val entries = (1 to 10).map(i => (new UniqueKeyWithHash(hash), s"value$i")) | ||
var map = new BadMap[UniqueKeyWithHash, String]() | ||
|
||
for ((key, value) <- entries) { | ||
map = map.copyingPut(key, value) | ||
} | ||
|
||
for ((key, value) <- entries) { | ||
assertEquals(value, map.get(key)) | ||
} | ||
} | ||
|
||
@Test | ||
def putSameHashModLength(): Unit = { | ||
// given that the table will eventually be the following size, we insert entries who should | ||
// eventually all share the same index and then later be redistributed once rehashed | ||
val size = 11 | ||
val entries = (1 to size * 2).map(i => (new UniqueKeyWithHash(size * i), s"value$i")) | ||
var map = new BadMap[UniqueKeyWithHash, String]() | ||
|
||
for ((key, value) <- entries) { | ||
map = map.copyingPut(key, value) | ||
} | ||
|
||
for ((key, value) <- entries) { | ||
assertEquals(value, map.get(key)) | ||
} | ||
} | ||
|
||
private class UniqueKeyWithHash(hash: Int) { | ||
override def hashCode(): Int = hash | ||
} | ||
} |