JS: reduce HashMap memory footprint

This commit is contained in:
Anton Bannykh
2017-07-13 15:15:33 +03:00
parent 070e35bc15
commit 6a5d8dcc82
3 changed files with 95 additions and 37 deletions
@@ -37,24 +37,39 @@ import kotlin.collections.AbstractMutableMap.SimpleEntry
*/ */
internal class InternalHashCodeMap<K, V>(override val equality: EqualityComparator) : InternalMap<K, V> { internal class InternalHashCodeMap<K, V>(override val equality: EqualityComparator) : InternalMap<K, V> {
private var backingMap: dynamic = js("Object.create(null)") private var backingMap: dynamic = createJsMap()
override var size: Int = 0 override var size: Int = 0
private set private set
override fun put(key: K, value: V): V? { override fun put(key: K, value: V): V? {
val hashCode = equality.getHashCode(key) val hashCode = equality.getHashCode(key)
val chain = getChainOrNull(hashCode) val chainOrEntry = getChainOrEntryOrNull(hashCode)
if (chain == null) { if (chainOrEntry == null) {
// This is a new chain, put it to the map. // This is a new chain, put it to the map.
backingMap[hashCode] = arrayOf(SimpleEntry(key, value)) backingMap[hashCode] = SimpleEntry(key, value)
} }
else { else {
// Chain already exists, perhaps key also exists. if (chainOrEntry !is Array<*>) {
val entry = chain.findEntryInChain(key) // It is an entry
if (entry != null) { val entry: SimpleEntry<K, V> = chainOrEntry
return entry.setValue(value) if (equality.equals(entry.key, key)) {
return entry.setValue(value)
}
else {
backingMap[hashCode] = arrayOf(entry, SimpleEntry(key, value))
size++
return null
}
}
else {
// Chain already exists, perhaps key also exists.
val chain: Array<MutableEntry<K, V>> = chainOrEntry
val entry = chain.findEntryInChain(key)
if (entry != null) {
return entry.setValue(value)
}
chain.asDynamic().push(SimpleEntry(key, value))
} }
chain.asDynamic().push(SimpleEntry(key, value))
} }
size++ size++
// structureChanged(host) // structureChanged(host)
@@ -63,29 +78,42 @@ internal class InternalHashCodeMap<K, V>(override val equality: EqualityComparat
override fun remove(key: K): V? { override fun remove(key: K): V? {
val hashCode = equality.getHashCode(key) val hashCode = equality.getHashCode(key)
val chain = getChainOrNull(hashCode) ?: return null val chainOrEntry = getChainOrEntryOrNull(hashCode) ?: return null
for (index in 0..chain.size-1) { if (chainOrEntry !is Array<*>) {
val entry = chain[index] val entry: MutableEntry<K, V> = chainOrEntry
if (equality.equals(key, entry.key)) { if (equality.equals(entry.key, key)) {
if (chain.size == 1) { deleteProperty(backingMap, hashCode)
chain.asDynamic().length = 0
// remove the whole array
deleteProperty(backingMap, hashCode)
}
else {
// splice out the entry we're removing
chain.asDynamic().splice(index, 1)
}
size-- size--
// structureChanged(host)
return entry.value return entry.value
} }
else {
return null
}
}
else {
val chain: Array<MutableEntry<K, V>> = chainOrEntry
for (index in 0..chain.size - 1) {
val entry = chain[index]
if (equality.equals(key, entry.key)) {
if (chain.size == 1) {
chain.asDynamic().length = 0
// remove the whole array
deleteProperty(backingMap, hashCode)
} else {
// splice out the entry we're removing
chain.asDynamic().splice(index, 1)
}
size--
// structureChanged(host)
return entry.value
}
}
} }
return null return null
} }
override fun clear() { override fun clear() {
backingMap = js("Object.create(null)") backingMap = createJsMap()
size = 0 size = 0
} }
@@ -93,8 +121,22 @@ internal class InternalHashCodeMap<K, V>(override val equality: EqualityComparat
override fun get(key: K): V? = getEntry(key)?.value override fun get(key: K): V? = getEntry(key)?.value
private fun getEntry(key: K): MutableEntry<K, V>? = private fun getEntry(key: K): MutableEntry<K, V>? {
getChainOrNull(equality.getHashCode(key))?.findEntryInChain(key) val chainOrEntry = getChainOrEntryOrNull(equality.getHashCode(key)) ?: return null
if (chainOrEntry !is Array<*>) {
val entry: MutableEntry<K, V> = chainOrEntry
if (equality.equals(entry.key, key)) {
return entry
}
else {
return null
}
}
else {
val chain: Array<MutableEntry<K, V>> = chainOrEntry
return chain.findEntryInChain(key)
}
}
private fun Array<MutableEntry<K, V>>.findEntryInChain(key: K): MutableEntry<K, V>? = private fun Array<MutableEntry<K, V>>.findEntryInChain(key: K): MutableEntry<K, V>? =
firstOrNull { entry -> equality.equals(entry.key, key) } firstOrNull { entry -> equality.equals(entry.key, key) }
@@ -107,23 +149,26 @@ internal class InternalHashCodeMap<K, V>(override val equality: EqualityComparat
val keys: Array<Int> = js("Object").keys(backingMap) val keys: Array<Int> = js("Object").keys(backingMap)
var keyIndex = -1 var keyIndex = -1
var chain: Array<MutableEntry<K, V>>? = null var chainOrEntry: dynamic = null
var isChain = false
var itemIndex = -1 var itemIndex = -1
var lastEntry: MutableEntry<K, V>? = null var lastEntry: MutableEntry<K, V>? = null
private fun computeNext(): Int { private fun computeNext(): Int {
if (chain != null) { if (chainOrEntry != null && isChain) {
if (++itemIndex < chain!!.size) val chainSize: Int = chainOrEntry.unsafeCast<Array<MutableEntry<K, V>>>().size
if (++itemIndex < chainSize)
return 0 return 0
} }
if (++keyIndex < keys.size) { if (++keyIndex < keys.size) {
chain = backingMap[keys[keyIndex]] chainOrEntry = backingMap[keys[keyIndex]]
isChain = chainOrEntry is Array<*>
itemIndex = 0 itemIndex = 0
return 0 return 0
} }
else { else {
chain = null chainOrEntry = null
return 1 return 1
} }
} }
@@ -136,7 +181,12 @@ internal class InternalHashCodeMap<K, V>(override val equality: EqualityComparat
override fun next(): MutableEntry<K, V> { override fun next(): MutableEntry<K, V> {
if (!hasNext()) throw NoSuchElementException() if (!hasNext()) throw NoSuchElementException()
val lastEntry = chain!![itemIndex] val lastEntry = if (isChain) {
chainOrEntry.unsafeCast<Array<MutableEntry<K, V>>>()[itemIndex]
}
else {
chainOrEntry.unsafeCast<MutableEntry<K, V>>()
}
this.lastEntry = lastEntry this.lastEntry = lastEntry
state = -1 state = -1
return lastEntry return lastEntry
@@ -152,9 +202,9 @@ internal class InternalHashCodeMap<K, V>(override val equality: EqualityComparat
} }
} }
private fun getChainOrNull(hashCode: Int): Array<MutableEntry<K, V>>? { private fun getChainOrEntryOrNull(hashCode: Int): dynamic {
val chain = backingMap[hashCode].unsafeCast<Array<MutableEntry<K, V>>?>() val chainOrEntry = backingMap[hashCode]
return chain.takeIf { it !== undefined } return if (chainOrEntry === undefined) null else chainOrEntry
} }
} }
@@ -28,4 +28,12 @@ internal interface InternalMap<K, V> : MutableIterable<MutableMap.MutableEntry<K
fun put(key: K, value: V): V? fun put(key: K, value: V): V?
fun remove(key: K): V? fun remove(key: K): V?
fun clear(): Unit fun clear(): Unit
fun createJsMap(): dynamic {
val result = js("Object.create(null)")
// force to switch object representation to dictionary mode
result["foo"] = 1;
deleteProperty(result, "foo")
return result
}
} }
@@ -30,7 +30,7 @@ import kotlin.collections.MutableMap.MutableEntry
*/ */
internal class InternalStringMap<K, V>(override val equality: EqualityComparator) : InternalMap<K, V> { internal class InternalStringMap<K, V>(override val equality: EqualityComparator) : InternalMap<K, V> {
private var backingMap: dynamic = js("Object.create(null)") private var backingMap: dynamic = createJsMap()
override var size: Int = 0 override var size: Int = 0
private set private set
@@ -87,7 +87,7 @@ internal class InternalStringMap<K, V>(override val equality: EqualityComparator
override fun clear() { override fun clear() {
backingMap = js("Object.create(null)") backingMap = createJsMap()
size = 0 size = 0
} }