package org.jctools.maps.nhbm_test;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
import org.jctools.maps.NonBlockingHashMapLong;
/*
* Written by Cliff Click and released to the public domain, as explained at
* http://creativecommons.org/licenses/publicdomain
* Big Chunks of code shamelessly copied from Doug Lea's test harness which is also public domain.
*/
public class perf_hashlong_test extends Thread {
static int _read_ratio, _gr, _pr;
static int _thread_min, _thread_max, _thread_incr;
static int _table_size;
static String KEYS[];
static volatile boolean _start;
static volatile boolean _stop;
static int check(String arg, String msg, int lower, int upper) throws Exception {
return check(Integer.parseInt(arg), msg, lower, upper);
}
static int check(int x, String msg, int lower, int upper) throws Exception {
if (x < lower || x > upper)
throw new Error(msg + " must be from " + lower + " to " + upper);
return x;
}
public static void main(String args[]) {
if (args.length == 0) {
args = new String[] { "75", "2", "8", "2", "100000" };
}
// Parse args
try {
_read_ratio = check(args[0], "read%", 0, 100);
_thread_min = check(args[1], "thread_min", 1, 100000);
_thread_max = check(args[2], "thread_max", 1, 100000);
_thread_incr = check(args[3], "thread_incr", 1, 100000);
_table_size = check(args[4], "table_size", 100, 100000000);
_gr = (_read_ratio << 20) / 100;
_pr = (((1 << 20) - _gr) >> 1) + _gr;
int trips = (_thread_max - _thread_min) / _thread_incr;
_thread_max = trips * _thread_incr + _thread_min;
}
catch (Exception e) {
System.out.println(
"Usage: perf_hash_test read%[0=churn test] thread-min thread-max thread-increment hash_table_size impl[All=0,Hashtable=1,HerlihyHashSet=2,CHM_16=3,CHM_256=4,CHM_4096=5,NonBlockingHashMap=6]");
throw new RuntimeException(e);
}
System.out.print(_read_ratio + "% gets, " + ((100 - _read_ratio) >> 1) + "% inserts, "
+ ((100 - _read_ratio) >> 1) + "% removes, " + "table_size=" + _table_size);
if (_read_ratio == 0)
System.out.print(" -- churn");
String name = "NonBlockingHashMapLong";
System.out.println(" " + name);
System.out.println("Threads from " + _thread_min + " to " + _thread_max + " by " + _thread_incr);
// Do some warmup
int keymax = 1;
while (keymax < _table_size)
keymax <<= 1;
if (_read_ratio == 0)
keymax = 1024 * 1024; // The churn test uses a large key set
KEYS = new String[keymax];
KEYS[0] = "Cliff0";
for (int i = 1; i < KEYS.length; i++) {
KEYS[i] = String.valueOf(i) + "abc" + String.valueOf(i * 17 + 123);
}
System.out.println("Warmup -variance: ");
run_till_stable(Math.min(_thread_min, 2), 1);
// Now do the real thing
System.out.print("==== Counter Threads Trial: ");
int num_trials = 7; // Number of Trials
for (int i = 0; i < num_trials; i++)
System.out.printf(" %3d ", i);
System.out.println(" Avg Stddev");
for (int i = _thread_min; i <= _thread_max; i += _thread_incr)
run_till_stable(i, num_trials);
}
static void run_till_stable(int num_threads, int num_trials) {
NonBlockingHashMapLong<String> HM = new NonBlockingHashMapLong<String>(true);
String name = "NonBlockingHashMapLong";
System.out.printf("=== %10.10s %3d cnts/sec=", name, num_threads);
// Quicky sanity check
for (int i = 0; i < 100; i++) {
HM.put(i, KEYS[i]);
for (int j = 0; j < i; j++) {
if (HM.get(j) != KEYS[j]) {
throw new Error("Broken table, put " + i + " but cannot find #" + j);
}
}
}
long[] trials = new long[num_trials]; // Number of trials
long total = 0;
for (int j = 0; j < trials.length; j++) {
long[] ops = new long[num_threads];
long[] nanos = new long[num_threads];
long millis = run_once(num_threads, HM, ops, nanos);
long sum = 0;
for (int i = 0; i < num_threads; i++)
sum += ops[i];
long ops_per_sec = (sum * 1000L) / millis;
trials[j] = ops_per_sec;
total += ops_per_sec;
System.out.printf(" %10d", ops_per_sec);
// for( int i=0; i<num_threads; i++ ) {
// if( nanos[i] < 1980000000 ||
// nanos[i] > 2010000000 ||
// ops[i] < 100000 )
// System.out.printf(" %d",ops[i]);
// }
}
if (trials.length > 2) {
// Toss out low & high
int lo = 0;
int hi = 0;
for (int j = 1; j < trials.length; j++) {
if (trials[lo] < trials[j])
lo = j;
if (trials[hi] > trials[j])
hi = j;
}
total -= (trials[lo] + trials[hi]);
trials[lo] = trials[trials.length - 1];
trials[hi] = trials[trials.length - 2];
// Print avg,stddev
long avg = total / (trials.length - 2);
long stddev = compute_stddev(trials, trials.length - 2);
long p = stddev * 100 / avg; // std-dev as a percent
if (trials.length - 2 > 2) {
// Toss out low & high
lo = 0;
hi = 0;
for (int j = 1; j < trials.length - 2; j++) {
if (trials[lo] < trials[j])
lo = j;
if (trials[hi] > trials[j])
hi = j;
}
total -= (trials[lo] + trials[hi]);
trials[lo] = trials[trials.length - 2 - 1];
trials[hi] = trials[trials.length - 2 - 2];
// Print avg,stddev
avg = total / (trials.length - 2 - 2);
stddev = compute_stddev(trials, trials.length - 2 - 2);
p = stddev * 100 / avg; // std-dev as a percent
}
System.out.printf(" %10d", avg);
System.out.printf(" (+/-%2d%%) %d", p, HM.size());
}
System.out.println();
}
static long compute_stddev(long[] trials, int len) {
double sum = 0;
double squ = 0.0;
for (int i = 0; i < len; i++) {
double d = (double) trials[i];
sum += d;
squ += d * d;
}
double x = squ - sum * sum / len;
double stddev = Math.sqrt(x / (len - 1));
return (long) stddev;
}
// Worker thread fields
final int _tnum;
final NonBlockingHashMapLong<String> _hash; // Shared hashtable
final long[] _ops;
final long[] _nanos;
public perf_hashlong_test() {
_tnum = 0;
_hash = null;
_ops = null;
_nanos = null;
}
private perf_hashlong_test(int tnum, NonBlockingHashMapLong<String> HM, long[] ops, long[] nanos) {
_tnum = tnum;
_hash = HM;
_ops = ops;
_nanos = nanos;
}
static long run_once(int num_threads, NonBlockingHashMapLong<String> HM, long[] ops, long[] nanos) {
Random R = new Random();
_start = false;
_stop = false;
HM.put(0, "Cliff0");
HM.remove(0);
int sz = HM.size();
while (sz + 1024 < _table_size) {
int idx = R.nextInt();
for (int i = 0; i < 1024; i++) {
int k = idx & (KEYS.length - 1);
HM.put(k, KEYS[k]);
idx++;
}
sz = HM.size();
}
while (sz < ((_table_size >> 1) + (_table_size >> 3))) {
int trip = 0;
int idx = R.nextInt();
while (true) {
int k = idx & (KEYS.length - 1);
String key = KEYS[k];
if (sz < _table_size) {
if (HM.put(k, key) == null) {
sz++;
break;
}
}
else {
if (HM.remove(k) != null) {
sz--;
break;
}
}
idx++;
if ((trip & 15) == 15)
idx = R.nextInt();
if (trip++ > 1024 * 1024) {
if (trip > 1024 * 1024 + 100)
throw new RuntimeException(
"barf trip " + sz + " " + HM.size() + " numkeys=" + KEYS.length);
System.out.println(key);
}
}
}
if (sz != HM.size()) {
throw new Error("size does not match table contents sz=" + sz + " size()=" + HM.size());
}
// Launch threads
// long nanoz = System.nanoTime();
// System.out.println(" "+nanoz+" Create-Threads");
perf_hashlong_test thrs[] = new perf_hashlong_test[num_threads];
for (int i = 0; i < num_threads; i++)
thrs[i] = new perf_hashlong_test(i, HM, ops, nanos);
for (int i = 0; i < num_threads; i++)
thrs[i].start();
// Run threads
// long nano = System.nanoTime();
// System.out.println(" "+nano+" Start");
long start = System.currentTimeMillis();
_start = true;
try {
Thread.sleep(2000);
}
catch (InterruptedException e) {
/* empty */}
_stop = true;
long stop = System.currentTimeMillis();
// long nanox = System.nanoTime();
long millis = stop - start;
// System.out.println(" "+nanox+" Stop");
for (int i = 0; i < num_threads; i++)
try {
thrs[i].join();
}
catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
// long nanoy = System.nanoTime();
// System.out.println(" "+nanoy+" Join-Done");
return millis;
}
// What a worker thread does
public void run() {
if (_read_ratio == 0) {
run_churn();
}
else {
run_normal();
}
}
// Force a large turnover of live keys, while keeping the total live-set
// low. 10 keys kept alive per thread, out of a set of a million or so.
// constantly churned, so we constantly need to 'cleanse' the table to flush
// old entries.
public void run_churn() {
int reprobe = System.identityHashCode(Thread.currentThread());
int idx = reprobe;
while (!_start) // Spin till Time To Go
try {
Thread.sleep(1);
}
catch (Exception e) {
/* empty */}
long nano1 = System.nanoTime();
int get_ops = 0;
int put_ops = 0;
int del_ops = 0;
while (!_stop) {
// Insert a key 10 probes in the future,
// remove a key 0 probes in the future,
// Net result is the thread keeps 10 random keys in table
int k1 = (idx + reprobe * 10) & (KEYS.length - 1);
String key1 = KEYS[k1];
_hash.put(k1, key1);
put_ops++;
// Remove a key 0 probes in the future
int k2 = idx & (KEYS.length - 1);
_hash.remove(k2);
del_ops++;
idx += reprobe;
}
// We stopped; report results into shared result structure
long nano2 = System.nanoTime();
int total = get_ops + put_ops + del_ops;
_ops[_tnum] = total;
_nanos[_tnum] = (nano2 - nano1);
}
public void run_normal() {
SimpleRandom R = new SimpleRandom();
while (!_start) // Spin till Time To Go
try {
Thread.sleep(1);
}
catch (Exception e) {
/* empty */}
long nano1 = System.nanoTime();
int get_ops = 0;
int put_ops = 0;
int del_ops = 0;
while (!_stop) {
int x = R.nextInt() & ((1 << 20) - 1);
int k = R.nextInt() & (KEYS.length - 1);
String key = KEYS[k];
if (x < _gr) {
get_ops++;
String val = _hash.get(k);
if (val != null && !val.equals(key))
throw new IllegalArgumentException("Mismatched key=" + key + " and val=" + val);
}
else if (x < _pr) {
put_ops++;
_hash.putIfAbsent(k, key);
// An interesting version: testing get immediately after putIfAbsent.
// Of course in a multi-threaded context it immediately throws false-positives.
// if( _hash.putIfAbsent( key, key ) == null )
// if( _hash.get(key) == null )
// throw new Error("putIfAbsent failed to put key=" + key + " for put_ops=" + put_ops + "and
// getops=" +get_ops + " del_ops="+del_ops);
}
else {
del_ops++;
_hash.remove(k);
}
}
// We stopped; report results into shared result structure
long nano2 = System.nanoTime();
int total = get_ops + put_ops + del_ops;
_ops[_tnum] = total;
_nanos[_tnum] = (nano2 - nano1);
}
// Fairly fast random numbers
public static final class SimpleRandom {
private final static long multiplier = 0x5DEECE66DL;
private final static long addend = 0xBL;
private final static long mask = (1L << 48) - 1;
static final AtomicLong seq = new AtomicLong(-715159705);
private long seed;
SimpleRandom() {
seed = System.nanoTime() + seq.getAndAdd(129);
}
public int nextInt() {
return next();
}
public int next() {
long nextseed = (seed * multiplier + addend) & mask;
seed = nextseed;
return ((int) (nextseed >>> 17)) & 0x7FFFFFFF;
}
}
}