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;
|
||||
|
||||
import static io.netty.util.internal.MathUtil.findNextPositivePowerOfTwo;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.AbstractCollection;
|
||||
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>> {
|
||||
|
||||
/** 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 */
|
||||
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 V[] values;
|
||||
private Collection<V> valueCollection;
|
||||
private int size;
|
||||
private int mask;
|
||||
|
||||
public @K@ObjectHashMap() {
|
||||
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;
|
||||
|
||||
// Adjust the initial capacity if necessary.
|
||||
int capacity = adjustCapacity(initialCapacity);
|
||||
int capacity = findNextPositivePowerOfTwo(initialCapacity);
|
||||
mask = capacity - 1;
|
||||
|
||||
// Allocate the arrays.
|
||||
keys = new @k@[capacity];
|
||||
@ -238,38 +241,33 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
|
||||
|
||||
@Override
|
||||
public Collection<V> values() {
|
||||
Collection<V> valueCollection = this.valueCollection;
|
||||
if (valueCollection == null) {
|
||||
this.valueCollection = valueCollection = new AbstractCollection<V>() {
|
||||
@Override
|
||||
public Iterator<V> iterator() {
|
||||
return new Iterator<V>() {
|
||||
final Iterator<Entry<V>> iter = @K@ObjectHashMap.this.iterator();
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iter.hasNext();
|
||||
}
|
||||
return new AbstractCollection<V>() {
|
||||
@Override
|
||||
public Iterator<V> iterator() {
|
||||
return new Iterator<V>() {
|
||||
final Iterator<Entry<V>> iter = @K@ObjectHashMap.this.iterator();
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iter.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V next() {
|
||||
return iter.next().value();
|
||||
}
|
||||
@Override
|
||||
public V next() {
|
||||
return iter.next().value();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return valueCollection;
|
||||
@Override
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -351,8 +349,7 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
|
||||
* Returns the hashed index for the given key.
|
||||
*/
|
||||
private int hashIndex(@k@ key) {
|
||||
// Allowing for negative keys by adding the length after the first mod operation.
|
||||
return (hashCode(key) % keys.length + keys.length) % keys.length;
|
||||
return hashCode(key) & mask;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -369,21 +366,13 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
|
||||
size++;
|
||||
|
||||
if (size > maxSize) {
|
||||
// Need to grow the arrays. We take care to detect integer overflow,
|
||||
// also limit array size to ArrayList.MAX_ARRAY_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);
|
||||
}
|
||||
}
|
||||
if(keys.length == Integer.MAX_VALUE) {
|
||||
throw new IllegalStateException("Max capacity reached at size=" + size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the given capacity value to ensure that it's odd. Even capacities can break probing.
|
||||
*/
|
||||
private static int adjustCapacity(int capacity) {
|
||||
return capacity | 1;
|
||||
// Double the capacity.
|
||||
rehash(keys.length << 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -444,6 +433,7 @@ public class @K@ObjectHashMap<V> implements @K@ObjectMap<V>, Iterable<@K@ObjectM
|
||||
values = temp;
|
||||
|
||||
maxSize = calcMaxSize(newCapacity);
|
||||
mask = newCapacity - 1;
|
||||
|
||||
// Insert to the new arrays.
|
||||
for (int i = 0; i < oldVals.length; ++i) {
|
||||
|
Loading…
Reference in New Issue
Block a user