Introduce OpenAddressLinearProbingHashTable to be used in binding context
See comments in the implementation for clarification
This commit is contained in:
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.util.slicedMap
|
||||
|
||||
import java.util.function.BiConsumer
|
||||
|
||||
// binary representation of fractional part of phi = (sqrt(5) - 1) / 2
|
||||
private const val MAGIC: Int = 0x9E3779B9L.toInt() // ((sqrt(5.0) - 1) / 2 * pow(2.0, 32.0)).toLong().toString(16)
|
||||
private const val MAX_SHIFT = 27
|
||||
private const val THRESHOLD = ((1L shl 31) - 1).toInt() // 50% fill factor for speed
|
||||
private val EMPTY_ARRAY = arrayOf<Any?>()
|
||||
|
||||
|
||||
// For more details see for Knuth's multiplicative hash with golden ratio
|
||||
// Shortly, we're trying to keep distribution of it uniform independently of input
|
||||
// It's necessary because we use very simple linear probing
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun Any.computeHash(shift: Int) = ((hashCode() * MAGIC) ushr shift) shl 1
|
||||
|
||||
/**
|
||||
* The main ideas that might lead to better locality:
|
||||
* - Storing values in the same array as keys
|
||||
* - Using linear probes to avoid jumping to to new random indices
|
||||
*
|
||||
* This Map implementation is not intended to follow some of the maps' contracts:
|
||||
* - `put` doesn't returns previous value
|
||||
* - `entries` is unsupported (use forEach instead)
|
||||
* - `remove` is unsupported
|
||||
*/
|
||||
internal class OpenAddressLinearProbingHashTable<K : Any, V : Any> : AbstractMutableMap<K, V>() {
|
||||
// fields be initialized later in `clear()`
|
||||
|
||||
// capacity = 1 << (32 - shift)
|
||||
private var shift = 0
|
||||
// keys are stored in even elements, values are in odd ones
|
||||
private var array = EMPTY_ARRAY
|
||||
private var size_ = 0
|
||||
|
||||
init {
|
||||
clear()
|
||||
}
|
||||
|
||||
override val size
|
||||
get() = size_
|
||||
|
||||
override fun get(key: K): V? {
|
||||
var i = key.computeHash(shift)
|
||||
var k = array[i]
|
||||
|
||||
while (true) {
|
||||
if (k === null) return null
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
if (k == key) return array[i + 1] as V
|
||||
if (i == 0) {
|
||||
i = array.size
|
||||
}
|
||||
i -= 2
|
||||
k = array[i]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Never returns previous values
|
||||
*/
|
||||
override fun put(key: K, value: V): V? {
|
||||
if (put(array, shift, key, value)) {
|
||||
if (++size_ >= (THRESHOLD ushr shift)) {
|
||||
rehash()
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun rehash() {
|
||||
val newShift = maxOf(shift - 3, 0)
|
||||
val newArraySize = 1 shl (33 - newShift)
|
||||
val newArray = arrayOfNulls<Any>(newArraySize)
|
||||
|
||||
var i = 0
|
||||
val arraySize = array.size
|
||||
while (i < arraySize) {
|
||||
val key = array[i]
|
||||
if (key != null) {
|
||||
put(newArray, newShift, key, array[i + 1])
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
|
||||
shift = newShift
|
||||
array = newArray
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
shift = MAX_SHIFT
|
||||
array = arrayOfNulls(1 shl (33 - shift))
|
||||
|
||||
size_ = 0
|
||||
}
|
||||
|
||||
override fun forEach(action: BiConsumer<in K, in V>) {
|
||||
var i = 0
|
||||
val arraySize = array.size
|
||||
while (i < arraySize) {
|
||||
val key = array[i]
|
||||
if (key != null) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
action.accept(key as K, array[i + 1] as V)
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
}
|
||||
|
||||
override val entries: MutableSet<MutableMap.MutableEntry<K, V>>
|
||||
get() = throw IllegalStateException("OpenAddressLinearProbingHashTable::entries is not supported and hardly will be")
|
||||
}
|
||||
|
||||
private fun put(array: Array<Any?>, aShift: Int, key: Any, value: Any?): Boolean {
|
||||
var i = key.computeHash(aShift)
|
||||
|
||||
while (true) {
|
||||
val k = array[i]
|
||||
if (k == null) {
|
||||
array[i] = key
|
||||
array[i + 1] = value
|
||||
return true
|
||||
}
|
||||
if (k == key) break
|
||||
if (i == 0) {
|
||||
i = array.size
|
||||
}
|
||||
i -= 2
|
||||
}
|
||||
|
||||
array[i + 1] = value
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.util.keyFMap.KeyFMap;
|
||||
import gnu.trove.THashMap;
|
||||
import kotlin.jvm.functions.Function3;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -32,7 +31,7 @@ import java.util.Map;
|
||||
public class SlicedMapImpl implements MutableSlicedMap {
|
||||
|
||||
private final boolean alwaysAllowRewrite;
|
||||
private final Map<Object, KeyFMap> map = new THashMap<>(0);
|
||||
private final Map<Object, KeyFMap> map = new OpenAddressLinearProbingHashTable<>();
|
||||
private Multimap<WritableSlice<?, ?>, Object> collectiveSliceKeys = null;
|
||||
|
||||
public SlicedMapImpl(boolean alwaysAllowRewrite) {
|
||||
@@ -101,18 +100,15 @@ public class SlicedMapImpl implements MutableSlicedMap {
|
||||
|
||||
@Override
|
||||
public void forEach(@NotNull Function3<WritableSlice, Object, Object, Void> f) {
|
||||
for (Map.Entry<Object, KeyFMap> entry : map.entrySet()) {
|
||||
Object key = entry.getKey();
|
||||
KeyFMap holder = entry.getValue();
|
||||
|
||||
if (holder == null) continue;
|
||||
map.forEach((key, holder) -> {
|
||||
if (holder == null) return;
|
||||
|
||||
for (Key<?> sliceKey : holder.getKeys()) {
|
||||
Object value = holder.get(sliceKey);
|
||||
|
||||
f.invoke(((AbstractWritableSlice) sliceKey).getSlice(), key, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -120,17 +116,15 @@ public class SlicedMapImpl implements MutableSlicedMap {
|
||||
public <K, V> ImmutableMap<K, V> getSliceContents(@NotNull ReadOnlySlice<K, V> slice) {
|
||||
ImmutableMap.Builder<K, V> builder = ImmutableMap.builder();
|
||||
|
||||
for (Map.Entry<Object, KeyFMap> entry : map.entrySet()) {
|
||||
|
||||
KeyFMap holder = entry.getValue();
|
||||
|
||||
map.forEach((key, holder) -> {
|
||||
V value = holder.get(slice.getKey());
|
||||
|
||||
if (value != null) {
|
||||
//noinspection unchecked
|
||||
builder.put((K) entry.getKey(), value);
|
||||
builder.put((K) key, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user