Optimizing KObjectHashMap hashIndex()
Motivation: The IntObjectHashMap benchmarks show the Agrona collections to be faster on put, lookup, and remove. One major difference is that we're using 2 modulus operations each time we increment the position index while iterating. Agrona uses a mask instead. Modifications: Modified the KObjectHashMap to use masking rather than modulus when wrapping the position index. This requires that the capacity be a power of 2. Result: Improved performance of IntObjectHashMap.
This commit is contained in:
parent
4e70523edd
commit
7aac50a79a
38
common/src/main/java/io/netty/util/internal/MathUtil.java
Normal file
38
common/src/main/java/io/netty/util/internal/MathUtil.java
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you 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 io.netty.util.internal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Math utility methods.
|
||||||
|
*/
|
||||||
|
public final class MathUtil {
|
||||||
|
|
||||||
|
private MathUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fast method of finding the next power of 2 greater than or equal to the supplied value.
|
||||||
|
*
|
||||||
|
* If the value is {@code <= 0} then 1 will be returned.
|
||||||
|
*
|
||||||
|
* This method is not suitable for {@link Integer#MIN_VALUE} or numbers greater than 2^30.*
|
||||||
|
* @param value from which to search for next power of 2
|
||||||
|
* @return The next power of 2 or the value itself if it is a power of 2
|
||||||
|
*/
|
||||||
|
public static int findNextPositivePowerOfTwo(final int value) {
|
||||||
|
assert value > Integer.MIN_VALUE && value < 0x40000000;
|
||||||
|
return 1 << (32 - Integer.numberOfLeadingZeros(value - 1));
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,8 @@
|
|||||||
|
|
||||||
package io.netty.util.collection;
|
package io.netty.util.collection;
|
||||||
|
|
||||||
|
import static io.netty.util.internal.MathUtil.findNextPositivePowerOfTwo;
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.util.AbstractCollection;
|
import java.util.AbstractCollection;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -33,7 +35,7 @@ import java.util.NoSuchElementException;
|
|||||||
public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectMap.Entry<V>> {
|
public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectMap.Entry<V>> {
|
||||||
|
|
||||||
/** Default initial capacity. Used if not specified in the constructor */
|
/** Default initial capacity. Used if not specified in the constructor */
|
||||||
public static final int DEFAULT_CAPACITY = 11;
|
public static final int DEFAULT_CAPACITY = 8;
|
||||||
|
|
||||||
/** Default load factor. Used if not specified in the constructor */
|
/** Default load factor. Used if not specified in the constructor */
|
||||||
public static final float DEFAULT_LOAD_FACTOR = 0.5f;
|
public static final float DEFAULT_LOAD_FACTOR = 0.5f;
|
||||||
@ -52,8 +54,8 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
|
|||||||
|
|
||||||
private @k@[] keys;
|
private @k@[] keys;
|
||||||
private V[] values;
|
private V[] values;
|
||||||
private Collection<V> valueCollection;
|
|
||||||
private int size;
|
private int size;
|
||||||
|
private int mask;
|
||||||
|
|
||||||
public @K@ObjectHashMap() {
|
public @K@ObjectHashMap() {
|
||||||
this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
|
this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
|
||||||
@ -76,7 +78,8 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
|
|||||||
this.loadFactor = loadFactor;
|
this.loadFactor = loadFactor;
|
||||||
|
|
||||||
// Adjust the initial capacity if necessary.
|
// Adjust the initial capacity if necessary.
|
||||||
int capacity = adjustCapacity(initialCapacity);
|
int capacity = findNextPositivePowerOfTwo(initialCapacity);
|
||||||
|
mask = capacity - 1;
|
||||||
|
|
||||||
// Allocate the arrays.
|
// Allocate the arrays.
|
||||||
keys = new @k@[capacity];
|
keys = new @k@[capacity];
|
||||||
@ -238,9 +241,7 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<V> values() {
|
public Collection<V> values() {
|
||||||
Collection<V> valueCollection = this.valueCollection;
|
return new AbstractCollection<V>() {
|
||||||
if (valueCollection == null) {
|
|
||||||
this.valueCollection = valueCollection = new AbstractCollection<V>() {
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<V> iterator() {
|
public Iterator<V> iterator() {
|
||||||
return new Iterator<V>() {
|
return new Iterator<V>() {
|
||||||
@ -269,9 +270,6 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return valueCollection;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
// Hashcode is based on all non-zero, valid keys. We have to scan the whole keys
|
// Hashcode is based on all non-zero, valid keys. We have to scan the whole keys
|
||||||
@ -351,8 +349,7 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
|
|||||||
* Returns the hashed index for the given key.
|
* Returns the hashed index for the given key.
|
||||||
*/
|
*/
|
||||||
private int hashIndex(@k@ key) {
|
private int hashIndex(@k@ key) {
|
||||||
// Allowing for negative keys by adding the length after the first mod operation.
|
return hashCode(key) & mask;
|
||||||
return (hashCode(key) % keys.length + keys.length) % keys.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -369,21 +366,13 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
|
|||||||
size++;
|
size++;
|
||||||
|
|
||||||
if (size > maxSize) {
|
if (size > maxSize) {
|
||||||
// Need to grow the arrays. We take care to detect integer overflow,
|
if(keys.length == Integer.MAX_VALUE) {
|
||||||
// also limit array size to ArrayList.MAX_ARRAY_SIZE.
|
throw new IllegalStateException("Max capacity reached at size=" + size);
|
||||||
rehash(adjustCapacity((int) Math.min(keys.length * 2.0, Integer.MAX_VALUE - 8)));
|
|
||||||
} else if (size == keys.length) {
|
|
||||||
// Open addressing requires that we have at least 1 slot available. Need to refresh
|
|
||||||
// the arrays to clear any removed elements.
|
|
||||||
rehash(keys.length);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Double the capacity.
|
||||||
* Adjusts the given capacity value to ensure that it's odd. Even capacities can break probing.
|
rehash(keys.length << 1);
|
||||||
*/
|
}
|
||||||
private static int adjustCapacity(int capacity) {
|
|
||||||
return capacity | 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -444,6 +433,7 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
|
|||||||
values = temp;
|
values = temp;
|
||||||
|
|
||||||
maxSize = calcMaxSize(newCapacity);
|
maxSize = calcMaxSize(newCapacity);
|
||||||
|
mask = newCapacity - 1;
|
||||||
|
|
||||||
// Insert to the new arrays.
|
// Insert to the new arrays.
|
||||||
for (int i = 0; i < oldVals.length; ++i) {
|
for (int i = 0; i < oldVals.length; ++i) {
|
||||||
|
Loading…
Reference in New Issue
Block a user