Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Cliff Moon
committed
Aug 31, 2010
0 parents
commit 5180b95
Showing
70 changed files
with
20,756 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.classpath | ||
.cvsignore | ||
.project | ||
bin/* | ||
lib/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
|
||
IF YOU ARE LOOKING for the drop-in replacement for java.util.Hashtable, it's | ||
in the lib directory, lib/java_util_hashtable.jar. It needs to be in your | ||
bootclasspath. Example: | ||
|
||
java -Xbootclasspath/p:lib/java_util_hashtable.jar my_java_app_goes_here | ||
|
||
|
||
--- | ||
|
||
A collection of Concurrent and Highly Scalable Utilities. These are intended | ||
as direct replacements for the java.util.* or java.util.concurrent.* | ||
collections but with better performance when many CPUs are using the | ||
collection concurrently. Single-threaded performance may be slightly lower. | ||
|
||
The direct replacements match the API - but not all behaviors are covered by | ||
the API, and so they may not work for your program. In particular, the | ||
replacement for java.util.Hashtable is NOT synchronized (that is the point!), | ||
although it is multi-threaded safe. If you rely on the undocumented | ||
synchronization behavior of the JDK Hashtable, your program may not work. | ||
Similarly, the iteration order is different between this version and the JDK | ||
version (this exact issue broke the SpecJBB benchmark when the iteration order | ||
was changed slightly (via using a slightly different hash function) between | ||
JDK rev's). | ||
|
||
If you want to drop-in the non-blocking versions of Hashtable, HashMap or | ||
ConcurrentHashMap, you'll need to alter your bootclasspath - these classes | ||
come directly from your JDK and so are found via the System loader before any | ||
class-path hacks can be done. | ||
|
||
To replace the JDK implementation of Hashtable with a non-blocking version of | ||
Hashtable, add java_util_hashtable.jar to your java launch line: | ||
|
||
java -Xbootclasspath/p:lib/java_util_hashtable.jar my_app_goes_here | ||
|
||
Similarly for ConcurrentHashMap, add java_util_concurrent_chm.jar: | ||
|
||
java -Xbootclasspath/p:lib/java_util_concurrent_chm.jar my_app_goes_here | ||
|
||
|
||
The other utilities do not have direct JDK replacements; you need to call them | ||
out directly and place high_scale_lib.jar in your classpath: | ||
|
||
- NonBlockingHashMap - Fast, concurrent, lock-free HashMap. Linear scaling to 768 CPUs. | ||
- NonBlockingHashMapLong - Same as above, but using primitive 'long' keys | ||
- NonBlockingHashSet - A Set version of NBHM | ||
- NonBlockingSetInt - A fast fully concurrent BitVector | ||
- Counter - A simple counter that scales linearly even when extremely hot. | ||
Most simple counters are either unsynchronized (hence drop counts, generally | ||
really badly beyond 2 cpus), or are normally lock'd (hence bottleneck in the | ||
5-10 cpu range), or might use Atomic's (hence bottleneck in the 25-50 cpu | ||
range). This version scales linearly to 768 CPUs. | ||
|
||
|
||
|
||
Cliff Click | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
|
||
import java.util.concurrent.atomic.*; | ||
public final class AtomicCounter extends Counter { | ||
public String name() { return "Atomic"; } | ||
private final AtomicLong _cnt = new AtomicLong(); | ||
public long get(){ return _cnt.get(); } | ||
public void add( long x ) { _cnt.getAndAdd(x); } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Simple build line | ||
# | ||
set JAVA_HOME=/usr/local/j2sdk1.5.0_06 | ||
javac -classpath $JAVA_HOME/jre/lib/rt.jar:. harness.java org/cliffc/high_scale_lib/*.java ../org/cliffc/high_scale_lib/*.java | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
|
||
import org.cliffc.high_scale_lib.*; | ||
public final class CATCounter extends Counter { | ||
public String name() { return "CAT"; } | ||
private final ConcurrentAutoTable _tab = new ConcurrentAutoTable(); | ||
public long get(){ return _tab.get(); } | ||
public void add( long x ) { _tab.add(x); } | ||
public void print() { _tab.print(); } | ||
public int internal_size() { return _tab.internal_size(); } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
//package org.cliffc.high_scale_lib; | ||
public abstract class Counter { | ||
public abstract String name(); | ||
public abstract long get(); | ||
public abstract void add( long x ); | ||
public long pre_add ( long x ) { long l = get(); add(x); return l; } | ||
public long post_add( long x ) { add(x); long l = get(); return l; } | ||
public long post_inc() { return post_add( 1); } | ||
public long pre_dec() { return pre_add(-1); } | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
/* | ||
* Written by Cliff Click and released to the public domain, as explained at | ||
* http://creativecommons.org/licenses/publicdomain | ||
*/ | ||
|
||
public class Harness extends Thread { | ||
static int _thread_min, _thread_max, _thread_incr; | ||
static int _ctr_impl; | ||
|
||
static Counter make_ctr( final int impl ) { | ||
switch( impl ) { | ||
case 1: return new RaceyCounter(); | ||
case 2: return new SyncCounter(); | ||
case 3: return new LockCounter(); | ||
case 4: return new AtomicCounter(); | ||
case 5: return new UnsafeCounter(); | ||
case 6: return new StripeLockCounter( 8); | ||
case 7: return new StripeUnsafeCounter( 8); | ||
case 8: return new StripeLockCounter( 64); | ||
case 9: return new StripeUnsafeCounter( 64); | ||
case 10: return new StripeLockCounter(256); | ||
case 11: return new StripeUnsafeCounter(256); | ||
case 12: return new CATCounter(); | ||
default: | ||
throw new Error("Bad imple"); | ||
} | ||
} | ||
|
||
static volatile boolean _start; | ||
static volatile boolean _stop; | ||
static final int NUM_CPUS = Runtime.getRuntime().availableProcessors(); | ||
|
||
static int check( String arg, String msg, int lower, int upper ) { | ||
return check( Integer.parseInt(arg), msg, lower, upper ); | ||
} | ||
static int check( int x, String msg, int lower, int upper ) { | ||
if( x < lower || x > upper ) | ||
throw new Error(msg+" must be from "+lower+" to "+upper); | ||
return x; | ||
} | ||
|
||
public static void main( String args[] ) { | ||
// Parse args | ||
try { | ||
_thread_min = check( args[0], "thread_min", 1, 100000 ); | ||
_thread_max = check( args[1], "thread_max", 1, 100000 ); | ||
_thread_incr = check( args[2], "thread_incr", 1, 100000 ); | ||
_ctr_impl = check( args[3], "implementation", -1, 13 ); | ||
|
||
int trips = (_thread_max - _thread_min)/_thread_incr; | ||
_thread_max = trips*_thread_incr + _thread_min; | ||
|
||
} catch( Error e ) { | ||
System.out.println("Usage: harness thread-min thread-max thread-incr impl[All=0]"); | ||
throw e; | ||
} | ||
String name = _ctr_impl == 0 ? "ALL" : (_ctr_impl==-1 ? "Best" : make_ctr(_ctr_impl).name()); | ||
System.out.println("===== "+name+" ====="); | ||
System.out.println("Threads from "+_thread_min+" to "+_thread_max+" by "+_thread_incr); | ||
|
||
// Do some warmup | ||
System.out.println("==== Warmup -variance: "); | ||
run_till_stable(Math.min(_thread_min,2),1); | ||
|
||
// Now do the real thing | ||
int num_trials = 7; // Number of Trials | ||
System.out.print("==== Counter Threads Trial:"); | ||
for( int i=0; i<num_trials; i++ ) | ||
System.out.printf(" %3d ",i); | ||
System.out.println(" Average"); | ||
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 ) { | ||
if( _ctr_impl > 0 ) { | ||
run_till_stable(num_threads,num_trials,_ctr_impl); | ||
} else if( _ctr_impl == 0 ) { | ||
for( int impl=1;impl<13; impl++ ) | ||
run_till_stable(num_threads,num_trials,impl); | ||
System.out.println(); | ||
} else { | ||
run_till_stable(num_threads,num_trials,11); // big stripage Unsafe | ||
run_till_stable(num_threads,num_trials,12); // CAT | ||
} | ||
} | ||
|
||
static void run_till_stable( int num_threads, int num_trials, int impl ) { | ||
|
||
Counter C = make_ctr(impl); | ||
System.out.printf("=== %10.10s %3d cnts/sec=",C.name(),num_threads); | ||
long[] trials = new long[num_trials]; // Number of trials | ||
long total_ops = 0; // Total ops altogether | ||
long total_ops_sec = 0; // Sum of ops/sec for each run | ||
|
||
// Run some trials | ||
for( int j=0; j<trials.length; j++ ) { | ||
long[] ops = new long[num_threads]; | ||
long millis = run_once(num_threads,C,ops); | ||
long sum = 0; | ||
for( int i=0; i<num_threads; i++ ) | ||
sum += ops[i]; | ||
total_ops += sum; | ||
sum = sum*1000L/millis; | ||
trials[j] = sum; | ||
total_ops_sec += sum; | ||
System.out.printf(" %10d",sum); | ||
} | ||
|
||
// Compute nice trial results | ||
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; | ||
} | ||
long total2 = total_ops_sec - (trials[lo]+trials[hi]); | ||
trials[lo] = trials[trials.length-1]; | ||
trials[hi] = trials[trials.length-2]; | ||
// Print avg,stddev | ||
long avg = total2/(trials.length-2); | ||
long stddev = compute_stddev(trials,trials.length-2); | ||
long p = stddev*100/avg; // std-dev as a percent | ||
|
||
System.out.printf(" %10d",avg); | ||
System.out.printf(" (+/-%2d%%)",p); | ||
} | ||
|
||
long loss = total_ops - C.get(); | ||
if( loss != 0 ) { | ||
System.out.print(" Lossage="); | ||
int loss_per = (int)(loss*100/total_ops); | ||
System.out.print(loss_per == 0 ? (""+loss) : (""+loss_per+"%")); | ||
} | ||
|
||
if( C instanceof CATCounter ) { | ||
CATCounter cat = (CATCounter)C; | ||
System.out.print(" autotable="+ cat.internal_size()); | ||
if( loss != 0 ) cat.print(); | ||
} | ||
|
||
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; | ||
} | ||
|
||
final int _tnum; | ||
final Counter _C; | ||
final long[] _ops; | ||
Harness( int tnum, Counter C, long[] ops ) { _tnum = tnum; _C = C; _ops = ops; } | ||
|
||
static long run_once( int num_threads, Counter C, long[] ops ) { | ||
_start = false; | ||
_stop = false; | ||
|
||
// Launch threads | ||
Harness thrs[] = new Harness[num_threads]; | ||
for( int i=0; i<num_threads; i++ ) { | ||
thrs[i] = new Harness(i, C, ops); | ||
//int h1 = System.identityHashCode(thrs[i]); | ||
//int h2 = h1; | ||
//h2 ^= (h2>>>20) ^ (h2>>>12); | ||
//h2 ^= (h2>>> 7) ^ (h2>>> 4); | ||
//System.out.printf("%x ",h1&0xfff); | ||
} | ||
//System.out.println(""); | ||
for( int i=0; i<num_threads; i++ ) | ||
thrs[i].start(); | ||
// Run threads | ||
long start = System.currentTimeMillis(); | ||
_start = true; | ||
try { Thread.sleep(2000); } catch( InterruptedException e ){} | ||
_stop = true; | ||
long stop = System.currentTimeMillis(); | ||
long millis = stop-start; | ||
|
||
for( int i=0; i<num_threads; i++ ) { | ||
try { | ||
thrs[i].join(); | ||
} catch( InterruptedException e ) { } | ||
} | ||
return millis; | ||
} | ||
|
||
// What a worker thread does | ||
public void run() { | ||
while( !_start ) // Spin till Time To Go | ||
try { Thread.sleep(1); } catch( InterruptedException e ){} | ||
|
||
int ops = 0; | ||
while( !_stop ) { | ||
ops++; | ||
_C.add(1); | ||
} | ||
// We stopped; report results into shared result structure | ||
_ops[_tnum] = ops; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import java.util.concurrent.locks.*; | ||
|
||
public final class LockCounter extends Counter { | ||
public String name() { return "Lock"; } | ||
private final ReentrantLock _lock = new ReentrantLock(); | ||
private long _cnt; | ||
public long get(){ return _cnt; } | ||
public void add( long x ) { | ||
try { | ||
_lock.lock(); | ||
_cnt += x; | ||
} finally { | ||
_lock.unlock(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
|
||
A testing harness for scalable counters. | ||
|
||
There are some counter implementations in the local java/util which | ||
must be in the bootclasspath (they use Unsafe). | ||
|
||
The main counter is ConcurrentAutoTable.java, which is kept in the | ||
top-level java/util directory, so I end up with 2 java/util | ||
directories: one for the testing harness & strawman counter | ||
implementations, one for the main implementation. | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
public final class RaceyCounter extends Counter { | ||
private long _cnt; | ||
public long get(){ return _cnt; } | ||
public void add( long x ) { _cnt += x; } | ||
public String name() { return "Racey"; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
|
||
import java.util.concurrent.locks.*; | ||
|
||
public final class StripeLockCounter extends Counter { | ||
private final int _stripes; | ||
private final ReentrantLock[] _locks; | ||
private final long _cnts[]; | ||
StripeLockCounter(int stripes) { | ||
_stripes = stripes; | ||
_locks = new ReentrantLock[stripes]; | ||
_cnts = new long[stripes]; | ||
for( int i=0; i<stripes; i++ ) | ||
_locks[i] = new ReentrantLock(); | ||
} | ||
public String name() { return "Locks"+_stripes; } | ||
public long get() { | ||
long sum = 0; | ||
for( int i=0; i<_cnts.length; i++ ) | ||
sum += _cnts[i]; | ||
return sum; | ||
} | ||
public void add( long x ) { | ||
int hash = System.identityHashCode( Thread.currentThread()); | ||
int idx = hash & (_locks.length-1); | ||
final Lock l = _locks[idx]; | ||
try { | ||
l.lock(); | ||
_cnts [idx] += x; | ||
} finally { | ||
l.unlock(); | ||
} | ||
} | ||
} |
Oops, something went wrong.