package org.jctools.maps.nhbm_test.jmh;
import org.jctools.maps.NonBlockingHashMap;
import org.jctools.maps.nhbm_test.SimpleRandom;
import org.jctools.util.Pow2;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
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;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.ThreadParams;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@OutputTimeUnit(TimeUnit.SECONDS)
@BenchmarkMode({Mode.Throughput})
@Warmup(iterations = 2, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 6, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class ConcurrentMapThroughput {
/*
* Note that in NonBlockingHashMap, puts that update an entry to the same reference are
* short circuited and do not mutate the hash table. Such operations are equivalent to a get.
*/
@Param(value = {"NonBlockingHashMap", "ConcurrentHashMap"})
private String implementation;
@Param(value = "50")
private static int readRatio;
@Param(value = "100000")
private static int tableSize;
private static String testData[];
private static int _gr, _pr;
private Map<String, String> map;
@Setup(Level.Trial)
public void createMap(ThreadParams threads) {
validateParameters();
createImplementation(threads);
setRatios();
}
private void validateParameters() {
if (readRatio < 0 || readRatio > 100) {
throw new IllegalArgumentException("readRatio must be a value between 0 and 100");
}
if (tableSize < 100 || tableSize > Pow2.MAX_POW2) {
throw new IllegalArgumentException("tableSize must be a value between 100 and " + Pow2.MAX_POW2);
}
}
private void createImplementation(ThreadParams threads) {
if ("ConcurrentHashMap".equalsIgnoreCase(implementation)) {
map = new ConcurrentHashMap<String, String>(16, 0.75f, 16);
} else if ("NonBlockingHashMap".equalsIgnoreCase(implementation)) {
map = new NonBlockingHashMap<String, String>();
} else if ("HashMap".equalsIgnoreCase(implementation)){
map = threads.getGroupCount() == 1 ?
new HashMap<String, String>() : Collections.synchronizedMap(new HashMap<String, String>());
} else{
throw new IllegalArgumentException("Unsupported map: " + implementation);
}
}
private void setRatios() {
_gr = (readRatio << 20) / 100;
_pr = (((1 << 20) - _gr) >> 1) + _gr;
}
@Setup(Level.Trial)
public void prepareMap() {
testData = new String[Pow2.roundToPowerOfTwo(tableSize)];
for (int i = 0; i < testData.length; i++) {
testData[i] = String.valueOf(i) + "abc" + String.valueOf(i * 17 + 123);
}
Random rand = new Random();
int sz = map.size();
while (sz + 1024 < tableSize) {
int idx = rand.nextInt();
for (int i = 0; i < 1024; i++) {
String key = testData[idx & (testData.length - 1)];
map.put(key, key);
idx++;
}
sz = map.size();
}
while (sz < ((tableSize >> 1) + (tableSize >> 3))) {
int trip = 0;
int idx = rand.nextInt();
while (true) {
String key = testData[idx & (testData.length - 1)];
if (sz < tableSize) {
if (map.put(key, key) == null) {
sz++;
break;
}
} else {
if (map.remove(key) != null) {
sz--;
break;
}
}
idx++;
if ((trip & 15) == 15)
idx = rand.nextInt();
if (trip++ > 1024 * 1024) {
if (trip > 1024 * 1024 + 100)
throw new AssertionError(
String.format("barf trip %d %d numkeys=%d", sz, map.size(), testData.length));
System.out.println(key);
}
}
}
if (sz != map.size()) {
throw new AssertionError("size does not match table contents sz=" + sz + " size()=" + map.size());
}
}
@State(Scope.Thread)
public static class ThreadState {
private SimpleRandom random = new SimpleRandom();
int next() { return random.next(); }
}
@Benchmark
@Threads(2)
public String randomGetPutRemove(ThreadState state) {
String key = testData[state.next() & (testData.length - 1)];
int x = state.next() & ((1 << 20) - 1);
if (x < _gr) {
String val = map.get(key);
if (val != null && !val.equals(key))
throw new AssertionError("Mismatched key=" + key + " and val=" + val);
return val;
} else if (x < _pr) {
return map.putIfAbsent(key, key);
} else {
return map.remove(key);
}
}
}