/* * Copyright 2015 Goldman Sachs. * * 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 com.gs.collections.impl.jmh; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.concurrent.TimeUnit; import com.gs.collections.api.block.function.primitive.IntToObjectFunction; import com.gs.collections.api.list.MutableList; import com.gs.collections.api.list.primitive.MutableIntList; import com.gs.collections.api.map.primitive.MutableIntIntMap; import com.gs.collections.api.set.primitive.MutableIntSet; import com.gs.collections.impl.SpreadFunctions; import com.gs.collections.impl.jmh.runner.AbstractJMHTestRunner; import com.gs.collections.impl.list.mutable.FastList; import com.gs.collections.impl.list.mutable.primitive.IntArrayList; import com.gs.collections.impl.map.mutable.primitive.IntIntHashMap; import com.gs.collections.impl.set.mutable.primitive.IntHashSet; import net.openhft.koloboke.collect.map.IntIntMap; import net.openhft.koloboke.collect.map.hash.HashIntIntMaps; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; @State(Scope.Thread) @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.SECONDS) public class IntIntMapSmallStressTest extends AbstractJMHTestRunner { private static final int LOOP_COUNT = 100; private static final int KEY_COUNT = 500; private static final int MAP_SIZE = 1_000; @Param({"true", "false"}) public boolean fullyRandom; private IntIntMap intIntKoloboke; private MutableIntIntMap intIntGsc; private Map<Integer, Integer> integerIntegerJdk; private int[] gscIntKeysForMap; private int[] kolobokeIntKeysForMap; private Integer[] jdkIntKeysForMap; private int jdkIndex(int key) { return this.mask(key ^ (key >>> 16)); } private int kolobokeIndex(int key) { int h = key * 0x9e3779b9; return this.mask(h ^ h >> 16); } private int gscIndex(int element) { return this.mask(element); } private int gscIndexTwo(int element) { return this.mask(SpreadFunctions.intSpreadTwo(element)); } private int mask(int spread) { return spread & ((1 << 11) - 1); } @Setup public void setUp() { this.intIntKoloboke = HashIntIntMaps.newMutableMap(MAP_SIZE); this.intIntGsc = new IntIntHashMap(MAP_SIZE); this.integerIntegerJdk = new HashMap<>(MAP_SIZE); Random random = new Random(0x123456789ABCDL); int[] randomNumbersForMap = this.getRandomKeys(random).toArray(); int number = 23; int lower = Integer.MIN_VALUE; int upper = Integer.MAX_VALUE; this.kolobokeIntKeysForMap = this.fullyRandom ? randomNumbersForMap : this.getKolobokeArray(number, lower, upper, random); this.gscIntKeysForMap = this.fullyRandom ? randomNumbersForMap : this.getGSCArray(number, lower, upper, random); this.jdkIntKeysForMap = this.fullyRandom ? IntIntMapSmallStressTest.boxIntArray(randomNumbersForMap) : this.getJDKArray(lower, upper, random); for (int i = 0; i < KEY_COUNT; i++) { this.intIntKoloboke.put(this.kolobokeIntKeysForMap[i], 5); this.intIntGsc.put(this.gscIntKeysForMap[i], 5); this.integerIntegerJdk.put(this.jdkIntKeysForMap[i], 5); } this.shuffle(this.gscIntKeysForMap, random); this.shuffle(this.kolobokeIntKeysForMap, random); this.shuffle(this.jdkIntKeysForMap, random); } protected int[] getGSCArray(int number, int lower, int upper, Random random) { int[] gscCollisions = this.getGSCSequenceCollisions(number, lower, upper).toArray(); this.shuffle(gscCollisions, random); return gscCollisions; } protected MutableIntList getGSCSequenceCollisions(int number, int lower, int upper) { MutableIntList gscCollidingNumbers = new IntArrayList(); for (int i = lower; i < upper && gscCollidingNumbers.size() < KEY_COUNT; i++) { if (this.gscIndex(i) - this.gscIndex(number) >= 0 && this.gscIndex(i) - this.gscIndex(number) < 10 && (this.gscIndexTwo(i) - this.gscIndexTwo(number) >= 0) && (this.gscIndexTwo(i) - this.gscIndexTwo(number) < 10)) { gscCollidingNumbers.add(i); } } return gscCollidingNumbers; } protected Integer[] getJDKArray(int lower, int upper, Random random) { MutableList<Integer> collisions = this.getJDKSequenceCollisions(lower, upper); Integer[] jdkCollision = collisions.toArray(new Integer[collisions.size()]); this.shuffle(jdkCollision, random); return jdkCollision; } protected MutableList<Integer> getJDKSequenceCollisions(int lower, int upper) { MutableList<Integer> jdkCollidingNumbers = FastList.newList(); int slots = 1; // slots = KEY_COUNT / (1 << 32) / (1 << MAP_SIZE) + 1; MutableIntSet indices = new IntHashSet(); for (int i = lower; i < upper && jdkCollidingNumbers.size() < KEY_COUNT; i++) { int index = this.jdkIndex(i); if (indices.size() < slots) { indices.add(index); jdkCollidingNumbers.add(i); } else if (indices.contains(index)) { jdkCollidingNumbers.add(i); } } return jdkCollidingNumbers; } protected int[] getKolobokeArray(int number, int lower, int upper, Random random) { int[] kolobokeCollisions = this.getKolobokeSequenceCollisions(number, lower, upper).toArray(); this.shuffle(kolobokeCollisions, random); return kolobokeCollisions; } protected MutableIntList getKolobokeSequenceCollisions(int number, int lower, int upper) { MutableIntList kolobokeCollidingNumbers = new IntArrayList(); for (int i = lower; i < upper && kolobokeCollidingNumbers.size() < KEY_COUNT; i++) { int index = this.kolobokeIndex(i); if (index >= number && index <= number + 100) { kolobokeCollidingNumbers.add(i); } } return kolobokeCollidingNumbers; } protected MutableIntSet getRandomKeys(Random random) { MutableIntSet set = new IntHashSet(KEY_COUNT); while (set.size() < KEY_COUNT) { set.add(random.nextInt()); } return set; } @Benchmark public void jdkGet() { for (int j = 0; j < LOOP_COUNT; j++) { for (int i = 0; i < KEY_COUNT; i++) { if (this.integerIntegerJdk.get(this.jdkIntKeysForMap[i]) == null) { throw new AssertionError(this.jdkIntKeysForMap[i] + " not in map"); } } if (this.integerIntegerJdk.size() != KEY_COUNT) { throw new AssertionError("size is " + this.integerIntegerJdk.size()); } } } @Benchmark public void kolobokeGet() { for (int j = 0; j < LOOP_COUNT; j++) { for (int i = 0; i < KEY_COUNT; i++) { if (this.intIntKoloboke.get(this.kolobokeIntKeysForMap[i]) == this.intIntKoloboke.defaultValue()) { throw new AssertionError(this.kolobokeIntKeysForMap[i] + " not in map"); } } if (this.intIntKoloboke.size() != KEY_COUNT) { throw new AssertionError("size is " + this.intIntKoloboke.size()); } } } @Benchmark public void gscGet() { for (int j = 0; j < LOOP_COUNT; j++) { for (int i = 0; i < KEY_COUNT; i++) { if (this.intIntGsc.get(this.gscIntKeysForMap[i]) == 0) { throw new AssertionError(this.gscIntKeysForMap[i] + " not in map"); } } if (this.intIntGsc.size() != KEY_COUNT) { throw new AssertionError("size is " + this.intIntGsc.size()); } } } @Benchmark public void jdkPut() { for (int j = 0; j < LOOP_COUNT; j++) { Map<Integer, Integer> newMap = new HashMap<>(MAP_SIZE); for (int i = 0; i < KEY_COUNT; i++) { newMap.put(this.jdkIntKeysForMap[i], 4); } if (newMap.size() != KEY_COUNT) { throw new AssertionError("size is " + newMap.size()); } } } @Benchmark public void kolobokePut() { for (int j = 0; j < LOOP_COUNT; j++) { IntIntMap newMap = HashIntIntMaps.newMutableMap(MAP_SIZE); for (int i = 0; i < KEY_COUNT; i++) { newMap.put(this.kolobokeIntKeysForMap[i], 4); } if (newMap.size() != KEY_COUNT) { throw new AssertionError("size is " + newMap.size()); } } } @Benchmark public void gscPut() { for (int j = 0; j < LOOP_COUNT; j++) { MutableIntIntMap newMap = new IntIntHashMap(MAP_SIZE); for (int i = 0; i < KEY_COUNT; i++) { newMap.put(this.gscIntKeysForMap[i], 4); } if (newMap.size() != KEY_COUNT) { throw new AssertionError("size is " + newMap.size()); } } } @Benchmark public void gscRemove() { for (int j = 0; j < LOOP_COUNT; j++) { MutableIntIntMap newMap = new IntIntHashMap(this.intIntGsc); for (int i = 0; i < KEY_COUNT; i++) { newMap.remove(this.gscIntKeysForMap[i]); } if (newMap.size() != 0) { throw new AssertionError("size is " + newMap.size()); } } } @Benchmark public void jdkRemove() { for (int j = 0; j < LOOP_COUNT; j++) { Map<Integer, Integer> newMap = new HashMap<>(this.integerIntegerJdk); for (int i = 0; i < KEY_COUNT; i++) { newMap.remove(this.jdkIntKeysForMap[i]); } if (newMap.size() != 0) { throw new AssertionError("size is " + newMap.size()); } } } @Benchmark public void kolobokeRemove() { for (int j = 0; j < LOOP_COUNT; j++) { IntIntMap newMap = HashIntIntMaps.newMutableMap(this.intIntKoloboke); for (int i = 0; i < KEY_COUNT; i++) { newMap.remove(this.kolobokeIntKeysForMap[i]); } if (newMap.size() != 0) { throw new AssertionError("size is " + newMap.size()); } } } public void shuffle(int[] intArray, Random rnd) { for (int i = intArray.length; i > 1; i--) { IntIntMapSmallStressTest.swap(intArray, i - 1, rnd.nextInt(i)); } } public void shuffle(Integer[] integerArray, Random rnd) { for (int i = integerArray.length; i > 1; i--) { IntIntMapSmallStressTest.swap(integerArray, i - 1, rnd.nextInt(i)); } } private static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } private static void swap(Integer[] arr, int i, int j) { Integer tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } private static Integer[] boxIntArray(int[] arr) { MutableList<Integer> list = new IntArrayList(arr).collect((IntToObjectFunction<Integer>) Integer::valueOf); return list.toArray(new Integer[arr.length]); } }