diff --git a/compiler/frontend/src/org/jetbrains/kotlin/util/slicedMap/OpenAddressLinearProbingHashTable.kt b/compiler/frontend/src/org/jetbrains/kotlin/util/slicedMap/OpenAddressLinearProbingHashTable.kt new file mode 100644 index 00000000000..c792adf54ec --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/util/slicedMap/OpenAddressLinearProbingHashTable.kt @@ -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() + + +// 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 : AbstractMutableMap() { + // 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(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) { + 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> + get() = throw IllegalStateException("OpenAddressLinearProbingHashTable::entries is not supported and hardly will be") +} + +private fun put(array: Array, 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 +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/util/slicedMap/SlicedMapImpl.java b/compiler/frontend/src/org/jetbrains/kotlin/util/slicedMap/SlicedMapImpl.java index 34359784b17..1f52470d1d9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/util/slicedMap/SlicedMapImpl.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/util/slicedMap/SlicedMapImpl.java @@ -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 map = new THashMap<>(0); + private final Map map = new OpenAddressLinearProbingHashTable<>(); private Multimap, Object> collectiveSliceKeys = null; public SlicedMapImpl(boolean alwaysAllowRewrite) { @@ -101,18 +100,15 @@ public class SlicedMapImpl implements MutableSlicedMap { @Override public void forEach(@NotNull Function3 f) { - for (Map.Entry 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 ImmutableMap getSliceContents(@NotNull ReadOnlySlice slice) { ImmutableMap.Builder builder = ImmutableMap.builder(); - for (Map.Entry 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(); } }