From 7aac50a79ac5e3d8b90808fb4102dddf8369ed6c Mon Sep 17 00:00:00 2001 From: nmittler Date: Mon, 13 Apr 2015 15:17:51 -0700 Subject: [PATCH] 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. --- .../java/io/netty/util/internal/MathUtil.java | 38 ++++++++ .../util/collection/KObjectHashMap.template | 86 ++++++++----------- 2 files changed, 76 insertions(+), 48 deletions(-) create mode 100644 common/src/main/java/io/netty/util/internal/MathUtil.java diff --git a/common/src/main/java/io/netty/util/internal/MathUtil.java b/common/src/main/java/io/netty/util/internal/MathUtil.java new file mode 100644 index 0000000000..b6d2ae0a7c --- /dev/null +++ b/common/src/main/java/io/netty/util/internal/MathUtil.java @@ -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)); + } +} diff --git a/common/src/main/templates/io/netty/util/collection/KObjectHashMap.template b/common/src/main/templates/io/netty/util/collection/KObjectHashMap.template index 291234d016..fa0a420372 100644 --- a/common/src/main/templates/io/netty/util/collection/KObjectHashMap.template +++ b/common/src/main/templates/io/netty/util/collection/KObjectHashMap.template @@ -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 implements @K@ObjectMap, Iterable<@K@ObjectMap.Entry> { /** 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 implements @K@ObjectMap, Iterable<@K@ObjectM private @k@[] keys; private V[] values; - private Collection valueCollection; private int size; + private int mask; public @K@ObjectHashMap() { this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); @@ -76,7 +78,8 @@ public class @K@ObjectHashMap implements @K@ObjectMap, 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 implements @K@ObjectMap, Iterable<@K@ObjectM @Override public Collection values() { - Collection valueCollection = this.valueCollection; - if (valueCollection == null) { - this.valueCollection = valueCollection = new AbstractCollection() { - @Override - public Iterator iterator() { - return new Iterator() { - final Iterator> iter = @K@ObjectHashMap.this.iterator(); - @Override - public boolean hasNext() { - return iter.hasNext(); - } + return new AbstractCollection() { + @Override + public Iterator iterator() { + return new Iterator() { + final Iterator> 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 implements @K@ObjectMap, 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 implements @K@ObjectMap, 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 implements @K@ObjectMap, Iterable<@K@ObjectM values = temp; maxSize = calcMaxSize(newCapacity); + mask = newCapacity - 1; // Insert to the new arrays. for (int i = 0; i < oldVals.length; ++i) {