summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordibyendumajumdar <mobile@majumdar.org.uk>2023-08-20 00:53:26 +0100
committerdibyendumajumdar <mobile@majumdar.org.uk>2023-08-20 00:53:26 +0100
commitc0250dbac620448ec4909615e9c571eece6be08b (patch)
tree8282810855597ce0f7fdaec6580ddf52658d7b2b
parent293506c1bd594f3d7a6a71bcd9aa8f140fbd13ab (diff)
downloadSimple-c0250dbac620448ec4909615e9c571eece6be08b.tar.gz
Simple-c0250dbac620448ec4909615e9c571eece6be08b.zip
Added JCtools dependency and removed classes that were copied from there
Removed the hash quality checks in Util as these access internals of maps in JCTools
-rw-r--r--pom.xml6
-rw-r--r--src/main/java/com/seaofnodes/util/AbstractEntry.java63
-rw-r--r--src/main/java/com/seaofnodes/util/BitSetSparse.java2
-rw-r--r--src/main/java/com/seaofnodes/util/ConcurrentAutoTable.java229
-rw-r--r--src/main/java/com/seaofnodes/util/IHashMap.java4
-rw-r--r--src/main/java/com/seaofnodes/util/NonBlockingHashMap.java1446
-rw-r--r--src/main/java/com/seaofnodes/util/NonBlockingHashMapLong.java1367
-rw-r--r--src/main/java/com/seaofnodes/util/Util.java56
8 files changed, 11 insertions, 3162 deletions
diff --git a/pom.xml b/pom.xml
index 177c3508..99349179 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,6 +20,12 @@
<dependencies>
<dependency>
+ <groupId>org.jctools</groupId>
+ <artifactId>jctools-core</artifactId>
+ <version>4.0.1</version>
+ </dependency>
+
+ <dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.1</version>
diff --git a/src/main/java/com/seaofnodes/util/AbstractEntry.java b/src/main/java/com/seaofnodes/util/AbstractEntry.java
deleted file mode 100644
index 616fb9d0..00000000
--- a/src/main/java/com/seaofnodes/util/AbstractEntry.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is Cliff Click.
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-
-package com.seaofnodes.util;
-
-import java.util.Map;
-
-/**
- * A simple implementation of {@link java.util.Map.Entry}.
- * Does not implement {@link java.util.Map.Entry#setValue}, that is done by users of the class.
- *
- * @author Cliff Click
- * @param <TypeK> the type of keys maintained by this map
- * @param <TypeV> the type of mapped values
- */
-
-abstract class AbstractEntry<TypeK,TypeV> implements Map.Entry<TypeK,TypeV> {
- /** Strongly typed key */
- protected final TypeK _key;
- /** Strongly typed value */
- protected TypeV _val;
-
- public AbstractEntry(final TypeK key, final TypeV val) { _key = key; _val = val; }
- public AbstractEntry(final Map.Entry<TypeK,TypeV> e ) { _key = e.getKey(); _val = e.getValue(); }
- /** Return "key=val" string */
- public String toString() { return _key + "=" + _val; }
- /** Return key */
- public TypeK getKey () { return _key; }
- /** Return val */
- public TypeV getValue() { return _val; }
-
- /** Equal if the underlying key & value are equal */
- public boolean equals(final Object o) {
- if (!(o instanceof Map.Entry)) return false;
- final Map.Entry e = (Map.Entry)o;
- return eq(_key, e.getKey()) && eq(_val, e.getValue());
- }
-
- /** Compute <code>"key.hashCode() ^ val.hashCode()"</code> */
- public int hashCode() {
- return
- ((_key == null) ? 0 : _key.hashCode()) ^
- ((_val == null) ? 0 : _val.hashCode());
- }
-
- private static boolean eq(final Object o1, final Object o2) {
- return (o1 == null ? o2 == null : o1.equals(o2));
- }
-}
diff --git a/src/main/java/com/seaofnodes/util/BitSetSparse.java b/src/main/java/com/seaofnodes/util/BitSetSparse.java
index 75438c3f..09d2f039 100644
--- a/src/main/java/com/seaofnodes/util/BitSetSparse.java
+++ b/src/main/java/com/seaofnodes/util/BitSetSparse.java
@@ -17,6 +17,8 @@
package com.seaofnodes.util;
+import org.jctools.maps.NonBlockingHashMapLong;
+
// Simple sparse bitset, with a test-and-set.
public class BitSetSparse {
NonBlockingHashMapLong<String> _set = new NonBlockingHashMapLong<>();
diff --git a/src/main/java/com/seaofnodes/util/ConcurrentAutoTable.java b/src/main/java/com/seaofnodes/util/ConcurrentAutoTable.java
deleted file mode 100644
index 061255be..00000000
--- a/src/main/java/com/seaofnodes/util/ConcurrentAutoTable.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/**
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is Cliff Click.
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-
-package com.seaofnodes.util;
-
-import java.io.Serializable;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-import java.util.Arrays;
-
-import sun.misc.Unsafe;
-
-/**
- * An auto-resizing table of {@code longs}, supporting low-contention CAS
- * operations. Updates are done with CAS's to no particular table element.
- * The intent is to support highly scalable counters, r/w locks, and other
- * structures where the updates are associative, loss-free (no-brainer), and
- * otherwise happen at such a high volume that the cache contention for
- * CAS'ing a single word is unacceptable.
- *
- * @since 1.5
- * @author Cliff Click
- */
-public class ConcurrentAutoTable implements Serializable {
-
- // --- public interface ---
-
- /**
- * Add the given value to current counter value. Concurrent updates will
- * not be lost, but addAndGet or getAndAdd are not implemented because the
- * total counter value (i.e., {@link #get}) is not atomically updated.
- * Updates are striped across an array of counters to avoid cache contention
- * and has been tested with performance scaling linearly up to 768 CPUs.
- */
- public void add( long x ) { add_if( x); }
- /** {@link #add} with -1 */
- public void decrement() { add_if(-1L); }
- /** {@link #add} with +1 */
- public void increment() { add_if( 1L); }
-
- /** Atomically set the sum of the striped counters to specified value.
- * Rather more expensive than a simple store, in order to remain atomic.
- */
- public void set( long x ) {
- CAT newcat = new CAT(null,4,x);
- // Spin until CAS works
- while( !CAS_cat(_cat,newcat) ) {/*empty*/}
- }
-
- /**
- * Not-atomic clear
- */
- public void clear() { Arrays.fill(_cat._t,0); }
- /**
- * Current value of the counter. Since other threads are updating furiously
- * the value is only approximate, but it includes all counts made by the
- * current thread. Requires a pass over the internally striped counters.
- */
- public long get() { return _cat.sum(); }
- /** Same as {@link #get}, included for completeness. */
- public int intValue() { return (int)_cat.sum(); }
- /** Same as {@link #get}, included for completeness. */
- public long longValue() { return _cat.sum(); }
-
- /**
- * A cheaper {@link #get}. Updated only once/millisecond, but as fast as a
- * simple load instruction when not updating.
- */
- public long estimate_get( ) { return _cat.estimate_sum(); }
-
- /**
- * Return the counter's {@code long} value converted to a string.
- */
- public String toString() { return _cat.toString(); }
-
- /**
- * A more verbose print than {@link #toString}, showing internal structure.
- * Useful for debugging.
- */
- public void print() { _cat.print(); }
-
- /**
- * Return the internal counter striping factor. Useful for diagnosing
- * performance problems.
- */
- public int internal_size() { return _cat._t.length; }
-
- // Only add 'x' to some slot in table, hinted at by 'hash'. The sum can
- // overflow. Value is CAS'd so no counts are lost. The CAS is retried until
- // it succeeds. Returned value is the old value.
- private long add_if( long x ) { return _cat.add_if(x,hash(),this); }
-
- // The underlying array of concurrently updated long counters
- private volatile CAT _cat = new CAT(null,1/*Start Small, Think Big!*/,0L);
- private static AtomicReferenceFieldUpdater<ConcurrentAutoTable,CAT> _catUpdater =
- AtomicReferenceFieldUpdater.newUpdater(ConcurrentAutoTable.class,CAT.class, "_cat");
- private boolean CAS_cat( CAT oldcat, CAT newcat ) { return _catUpdater.compareAndSet(this,oldcat,newcat); }
-
- // Hash spreader
- private static int hash() {
- //int h = (int)Thread.currentThread().getId();
- int h = System.identityHashCode(Thread.currentThread());
- return h<<3; // Pad out cache lines. The goal is to avoid cache-line contention
- }
-
- // --- CAT -----------------------------------------------------------------
- private static class CAT implements Serializable {
-
- // Unsafe crud: get a function which will CAS arrays
- private static final Unsafe _unsafe = UtilUnsafe.getUnsafe();
- private static final int _Lbase = _unsafe.arrayBaseOffset(long[].class);
- private static final int _Lscale = _unsafe.arrayIndexScale(long[].class);
- private static long rawIndex(long[] ary, int i) {
- assert i >= 0 && i < ary.length;
- return _Lbase + i * _Lscale;
- }
- private static boolean CAS( long[] A, int idx, long old, long nnn ) {
- return _unsafe.compareAndSwapLong( A, rawIndex(A,idx), old, nnn );
- }
-
- //volatile long _resizers; // count of threads attempting a resize
- //static private final AtomicLongFieldUpdater<CAT> _resizerUpdater =
- // AtomicLongFieldUpdater.newUpdater(CAT.class, "_resizers");
-
- private final CAT _next;
- private volatile long _fuzzy_sum_cache;
- private volatile long _fuzzy_time;
- private static final int MAX_SPIN=1;
- private final long[] _t; // Power-of-2 array of longs
-
- CAT( CAT next, int sz, long init ) {
- _next = next;
- _t = new long[sz];
- _t[0] = init;
- }
-
- // Only add 'x' to some slot in table, hinted at by 'hash'. The sum can
- // overflow. Value is CAS'd so no counts are lost. The CAS is attempted
- // ONCE.
- public long add_if( long x, int hash, ConcurrentAutoTable master ) {
- final long[] t = _t;
- final int idx = hash & (t.length-1);
- // Peel loop; try once fast
- long old = t[idx];
- final boolean ok = CAS( t, idx, old, old+x );
- if( ok ) return old; // Got it
- // Try harder
- int cnt=0;
- while( true ) {
- old = t[idx];
- if( CAS( t, idx, old, old+x ) ) break; // Got it!
- cnt++;
- }
- if( cnt < MAX_SPIN ) return old; // Allowable spin loop count
- if( t.length >= 1024*1024 ) return old; // too big already
-
- // Too much contention; double array size in an effort to reduce contention
- //long r = _resizers;
- //final int newbytes = (t.length<<1)<<3/*word to bytes*/;
- //while( !_resizerUpdater.compareAndSet(this,r,r+newbytes) )
- // r = _resizers;
- //r += newbytes;
- if( master._cat != this ) return old; // Already doubled, don't bother
- //if( (r>>17) != 0 ) { // Already too much allocation attempts?
- // // We could use a wait with timeout, so we'll wakeup as soon as the new
- // // table is ready, or after the timeout in any case. Annoyingly, this
- // // breaks the non-blocking property - so for now we just briefly sleep.
- // //synchronized( this ) { wait(8*megs); } // Timeout - we always wakeup
- // try { Thread.sleep(r>>17); } catch( InterruptedException e ) { }
- // if( master._cat != this ) return old;
- //}
-
- CAT newcat = new CAT(this,t.length*2,0);
- // Take 1 stab at updating the CAT with the new larger size. If this
- // fails, we assume some other thread already expanded the CAT - so we
- // do not need to retry until it succeeds.
- while( master._cat == this && !master.CAS_cat(this,newcat) ) {/*empty*/}
- return old;
- }
-
-
- // Return the current sum of all things in the table. Writers can be
- // updating the table furiously, so the sum is only locally accurate.
- public long sum( ) {
- long sum = _next == null ? 0 : _next.sum(); // Recursively get cached sum
- final long[] t = _t;
- for( long cnt : t ) sum += cnt;
- return sum;
- }
-
- // Fast fuzzy version. Used a cached value until it gets old, then re-up
- // the cache.
- public long estimate_sum( ) {
- // For short tables, just do the work
- if( _t.length <= 64 ) return sum();
- // For bigger tables, periodically freshen a cached value
- long millis = System.currentTimeMillis();
- if( _fuzzy_time != millis ) { // Time marches on?
- _fuzzy_sum_cache = sum(); // Get sum the hard way
- _fuzzy_time = millis; // Indicate freshness of cached value
- }
- return _fuzzy_sum_cache; // Return cached sum
- }
-
- public String toString( ) { return Long.toString(sum()); }
-
- public void print() {
- long[] t = _t;
- System.out.print("["+t[0]);
- for( int i=1; i<t.length; i++ )
- System.out.print(","+t[i]);
- System.out.print("]");
- if( _next != null ) _next.print();
- }
- }
-}
diff --git a/src/main/java/com/seaofnodes/util/IHashMap.java b/src/main/java/com/seaofnodes/util/IHashMap.java
index 0663c473..2f1a1d12 100644
--- a/src/main/java/com/seaofnodes/util/IHashMap.java
+++ b/src/main/java/com/seaofnodes/util/IHashMap.java
@@ -17,6 +17,8 @@
package com.seaofnodes.util;
+import org.jctools.maps.NonBlockingHashMap;
+
import java.util.Set;
@SuppressWarnings("unchecked")
@@ -26,7 +28,7 @@ public class IHashMap {
public <T> T put(T k, T v) { _map.put(k,v); return v; }
public <T> T get(T key) { return (T)_map.get(key); }
public void remove(Object key) { _map.remove(key); }
- public void clear() { _map.clear(true); }
+ public void clear() { _map.clear(); }
public boolean isEmpty() { return _map.isEmpty(); }
public <T> Set<T> keySet() { return _map.keySet(); }
}
diff --git a/src/main/java/com/seaofnodes/util/NonBlockingHashMap.java b/src/main/java/com/seaofnodes/util/NonBlockingHashMap.java
deleted file mode 100644
index b2ff5b70..00000000
--- a/src/main/java/com/seaofnodes/util/NonBlockingHashMap.java
+++ /dev/null
@@ -1,1446 +0,0 @@
-/**
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is Cliff Click.
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-
-package com.seaofnodes.util;
-
-import sun.misc.Unsafe;
-
-import java.io.IOException;
-import java.io.Serializable;
-import java.lang.reflect.Field;
-import java.util.*;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicLongFieldUpdater;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-
-/*
- * Written by Cliff Click and released to the public domain, as explained at
- * http://creativecommons.org/licenses/publicdomain
- */
-
-/**
- * A lock-free alternate implementation of {@link java.util.concurrent.ConcurrentHashMap}
- * with better scaling properties and generally lower costs to mutate the Map.
- * It provides identical correctness properties as ConcurrentHashMap. All
- * operations are non-blocking and multi-thread safe, including all update
- * operations. {@link NonBlockingHashMap} scales substantially better than
- * {@link java.util.concurrent.ConcurrentHashMap} for high update rates, even with a
- * large concurrency factor. Scaling is linear up to 768 CPUs on a 768-CPU
- * Azul box, even with 100% updates or 100% reads or any fraction in-between.
- * Linear scaling up to all cpus has been observed on a 32-way Sun US2 box,
- * 32-way Sun Niagara box, 8-way Intel box and a 4-way Power box.
- *
- * This class obeys the same functional specification as {@link
- * java.util.Hashtable}, and includes versions of methods corresponding to
- * each method of <tt>Hashtable</tt>. However, even though all operations are
- * thread-safe, operations do <em>not</em> entail locking and there is
- * <em>not</em> any support for locking the entire table in a way that
- * prevents all access. This class is fully interoperable with
- * <tt>Hashtable</tt> in programs that rely on its thread safety but not on
- * its synchronization details.
- *
- * <p> Operations (including <tt>put</tt>) generally do not block, so may
- * overlap with other update operations (including other <tt>puts</tt> and
- * <tt>removes</tt>). Retrievals reflect the results of the most recently
- * <em>completed</em> update operations holding upon their onset. For
- * aggregate operations such as <tt>putAll</tt>, concurrent retrievals may
- * reflect insertion or removal of only some entries. Similarly, Iterators
- * and Enumerations return elements reflecting the state of the hash table at
- * some point at or since the creation of the iterator/enumeration. They do
- * <em>not</em> throw {@link ConcurrentModificationException}. However,
- * iterators are designed to be used by only one thread at a time.
- *
- * <p> Very full tables, or tables with high reprobe rates may trigger an
- * internal resize operation to move into a larger table. Resizing is not
- * terribly expensive, but it is not free either; during resize operations
- * table throughput may drop somewhat. All threads that visit the table
- * during a resize will 'help' the resizing but will still be allowed to
- * complete their operation before the resize is finished (i.e., a simple
- * 'get' operation on a million-entry table undergoing resizing will not need
- * to block until the entire million entries are copied).
- *
- * <p>This class and its views and iterators implement all of the
- * <em>optional</em> methods of the {@link Map} and {@link Iterator}
- * interfaces.
- *
- * <p> Like {@link Hashtable} but unlike {@link HashMap}, this class
- * does <em>not</em> allow <tt>null</tt> to be used as a key or value.
- *
- *
- * @since 1.5
- * @author Cliff Click
- * @param <TypeK> the type of keys maintained by this map
- * @param <TypeV> the type of mapped values
- */
-
-public class NonBlockingHashMap<TypeK, TypeV>
- extends AbstractMap<TypeK, TypeV>
- implements ConcurrentMap<TypeK, TypeV>, Cloneable, Serializable {
-
- private static final long serialVersionUID = 1234123412341234123L;
-
- private static final int REPROBE_LIMIT=10; // Too many reprobes then force a table-resize
-
- // --- Bits to allow Unsafe access to arrays
- private static final Unsafe _unsafe = UtilUnsafe.getUnsafe();
- private static final int _Obase = _unsafe.arrayBaseOffset(Object[].class);
- private static final int _Oscale = _unsafe.arrayIndexScale(Object[].class);
- private static final int _Olog = _Oscale==4?2:(_Oscale==8?3:9999);
- private static long rawIndex(final Object[] ary, final int idx) {
- assert idx >= 0 && idx < ary.length;
- // Note the long-math requirement, to handle arrays of more than 2^31 bytes
- // - or 2^28 - or about 268M - 8-byte pointer elements.
- return _Obase + ((long)idx << _Olog);
- }
-
- // --- Setup to use Unsafe
- private static final long _kvs_offset;
- static { // <clinit>
- Field f = null;
- try { f = NonBlockingHashMap.class.getDeclaredField("_kvs"); }
- catch( java.lang.NoSuchFieldException e ) { throw new RuntimeException(e); }
- _kvs_offset = _unsafe.objectFieldOffset(f);
- }
- private boolean CAS_kvs( final Object[] oldkvs, final Object[] newkvs ) {
- return _unsafe.compareAndSwapObject(this, _kvs_offset, oldkvs, newkvs );
- }
-
- // --- Adding a 'prime' bit onto Values via wrapping with a junk wrapper class
- private static final class Prime {
- final Object _V;
- Prime( Object V ) { _V = V; }
- static Object unbox( Object V ) { return V instanceof Prime ? ((Prime)V)._V : V; }
- }
-
- // --- hash ----------------------------------------------------------------
- // Helper function to spread lousy hashCodes
- private static int hash( final Object key) {
- int h = key.hashCode(); // The real hashCode call
- h ^= (h>>>20) ^ (h>>>12);
- h ^= (h>>> 7) ^ (h>>> 4);
- return h;
- }
-
-
-
- // --- The Hash Table --------------------
- // Slot 0 is always used for a 'CHM' entry below to hold the interesting
- // bits of the hash table. Slot 1 holds full hashes as an array of ints.
- // Slots {2,3}, {4,5}, etc hold {Key,Value} pairs. The entire hash table
- // can be atomically replaced by CASing the _kvs field.
- //
- // Why is CHM buried inside the _kvs Object array, instead of the other way
- // around? The CHM info is used during resize events and updates, but not
- // during standard 'get' operations. I assume 'get' is much more frequent
- // than 'put'. 'get' can skip the extra indirection of skipping through the
- // CHM to reach the _kvs array.
- private transient Object[] _kvs;
- private static CHM chm ( Object[] kvs) { return (CHM )kvs[0]; }
- private static int[] hashes( Object[] kvs) { return (int[])kvs[1]; }
- // Number of K,V pairs in the table
- private static int len( Object[] kvs) { return (kvs.length-2)>>1; }
-
- // Time since last resize
- private transient long _last_resize_milli;
-
- // --- Minimum table size ----------------
- // Pick size 4 K/V pairs, which turns into (4*2+2)*4+12 = 54 bytes on a
- // standard 32-bit HotSpot, and (4*2+2)*8+12 = 92 bytes on 64-bit JVM.
- private static final int MIN_SIZE_LOG=2; //
- private static final int MIN_SIZE=(1<<MIN_SIZE_LOG); // Must be power of 2
-
- // --- Sentinels -------------------------
- // No-Match-Old - putIfMatch does updates only if it matches the old value,
- // and NO_MATCH_OLD basically counts as a wildcard match.
- private static final Object NO_MATCH_OLD = new Object(); // Sentinel
- // Match-Any-not-null - putIfMatch does updates only if it find a real old
- // value.
- private static final Object MATCH_ANY = new Object(); // Sentinel
- // This K/V pair has been deleted (but the Key slot is forever claimed).
- // The same Key can be reinserted with a new value later.
- public static final Object TOMBSTONE = new Object();
- // Prime'd or box'd version of TOMBSTONE. This K/V pair was deleted, then a
- // table resize started. The K/V pair has been marked so that no new
- // updates can happen to the old table (and since the K/V pair was deleted
- // nothing was copied to the new table).
- private static final Prime TOMBPRIME = new Prime(TOMBSTONE);
-
- // --- key,val -------------------------------------------------------------
- // Access K,V for a given idx
- //
- // Note that these are static, so that the caller is forced to read the _kvs
- // field only once, and share that read across all key/val calls - lest the
- // _kvs field move out from under us and back-to-back key & val calls refer
- // to different _kvs arrays.
- private static Object key( Object[] kvs, int idx) { return kvs[(idx<<1)+2]; }
- private static Object val( Object[] kvs, int idx) { return kvs[(idx<<1)+3]; }
- private static boolean CAS_key( Object[] kvs, int idx, Object old, Object key ) {
- return _unsafe.compareAndSwapObject( kvs, rawIndex(kvs,(idx<<1)+2), old, key );
- }
- private static boolean CAS_val( Object[] kvs, int idx, Object old, Object val ) {
- return _unsafe.compareAndSwapObject( kvs, rawIndex(kvs,(idx<<1)+3), old, val );
- }
-
-
- // --- dump ----------------------------------------------------------------
- /** Verbose printout of table internals, useful for debugging. */
- public final void print() {
- System.out.println("=========");
- print2(_kvs);
- System.out.println("=========");
- }
- // print the entire state of the table
- private void print( Object[] kvs ) {
- for( int i=0; i<len(kvs); i++ ) {
- Object K = key(kvs,i);
- if( K != null ) {
- String KS = (K == TOMBSTONE) ? "XXX" : K.toString();
- Object V = val(kvs,i);
- Object U = Prime.unbox(V);
- String p = (V==U) ? "" : "prime_";
- String US = (U == TOMBSTONE) ? "tombstone" : U.toString();
- System.out.println(""+i+" ("+KS+","+p+US+")");
- }
- }
- Object[] newkvs = chm(kvs)._newkvs; // New table, if any
- if( newkvs != null ) {
- System.out.println("----");
- print(newkvs);
- }
- }
- // print only the live values, broken down by the table they are in
- private final void print2( Object[] kvs) {
- for( int i=0; i<len(kvs); i++ ) {
- Object key = key(kvs,i);
- Object val = val(kvs,i);
- Object U = Prime.unbox(val);
- if( key != null && key != TOMBSTONE && // key is sane
- val != null && U != TOMBSTONE ) { // val is sane
- String p = (val==U) ? "" : "prime_";
- System.out.println(""+i+" ("+key+","+p+val+")");
- }
- }
- Object[] newkvs = chm(kvs)._newkvs; // New table, if any
- if( newkvs != null ) {
- System.out.println("----");
- print2(newkvs);
- }
- }
-
- // Count of reprobes
- private transient AryInt _reprobes = null;
-
- /** Get and clear the current count of reprobes. Reprobes happen on key
- * collisions, and a high reprobe rate may indicate a poor hash function or
- * weaknesses in the table resizing function.
- * @return the count of reprobes since the last call to {@link #reprobes}
- * or since the table was created. */
- public AryInt reprobes() { return _reprobes==null ? (_reprobes = new AryInt()) : _reprobes; }
- public int len() { return len(_kvs); }
- private void record( int ps ) { if( _reprobes!=null ) _record(ps); }
- private void _record( int ps ) { _reprobes.setX(ps,_reprobes.atX(ps)+1); }
-
-
- // --- reprobe_limit -----------------------------------------------------
- // Heuristic to decide if we have reprobed toooo many times. Running over
- // the reprobe limit on a 'get' call acts as a 'miss'; on a 'put' call it
- // can trigger a table resize. Several places must have exact agreement on
- // what the reprobe_limit is, so we share it here.
- private static int reprobe_limit( int len ) {
- return REPROBE_LIMIT + (len>>4);
- }
-
- // --- NonBlockingHashMap --------------------------------------------------
- // Constructors
-
- /** Create a new NonBlockingHashMap with default minimum size (currently set
- * to 8 K/V pairs or roughly 84 bytes on a standard 32-bit JVM). */
- public NonBlockingHashMap( ) { this(MIN_SIZE); }
-
- /** Create a new NonBlockingHashMap with initial room for the given number of
- * elements, thus avoiding internal resizing operations to reach an
- * appropriate size. Large numbers here when used with a small count of
- * elements will sacrifice space for a small amount of time gained. The
- * initial size will be rounded up internally to the next larger power of 2. */
- public NonBlockingHashMap( final int initial_sz ) { initialize(initial_sz); }
- private void initialize( int initial_sz ) {
- if( initial_sz < 0 ) throw new IllegalArgumentException();
- int i; // Convert to next largest power-of-2
- if( initial_sz > 1024*1024 ) initial_sz = 1024*1024;
- for( i=MIN_SIZE_LOG; (1<<i) < initial_sz; i++ ) ;
- // Double size for K,V pairs, add 1 for CHM and 1 for hashes
- _kvs = new Object[((1<<i)<<1)+2];
- _kvs[0] = new CHM(new ConcurrentAutoTable()); // CHM in slot 0
- _kvs[1] = new int[1<<i]; // Matching hash entries
- _last_resize_milli = System.currentTimeMillis();
- }
- // Version for subclassed readObject calls, to be called after the defaultReadObject
- protected final void initialize() { initialize(MIN_SIZE); }
-
- // --- wrappers ------------------------------------------------------------
-
- /** Returns the number of key-value mappings in this map.
- * @return the number of key-value mappings in this map */
- @Override
- public int size ( ) { return chm(_kvs).size(); }
- /** Returns <tt>size() == 0</tt>.
- * @return <tt>size() == 0</tt> */
- @Override
- public boolean isEmpty ( ) { return size() == 0; }
-
- /** Tests if the key in the table using the <tt>equals</tt> method.
- * @return <tt>true</tt> if the key is in the table using the <tt>equals</tt> method
- * @throws NullPointerException if the specified key is null */
- @Override
- public boolean containsKey( Object key ) { return get(key) != null; }
-
- /** Legacy method testing if some key maps into the specified value in this
- * table. This method is identical in functionality to {@link
- * #containsValue}, and exists solely to ensure full compatibility with
- * class {@link java.util.Hashtable}, which supported this method prior to
- * introduction of the Java Collections framework.
- * @param val a value to search for
- * @return <tt>true</tt> if this map maps one or more keys to the specified value
- * @throws NullPointerException if the specified value is null */
- public boolean contains ( Object val ) { return containsValue(val); }
-
- /** Maps the specified key to the specified value in the table. Neither key
- * nor value can be null.
- * <p> The value can be retrieved by calling {@link #get} with a key that is
- * equal to the original key.
- * @param key key with which the specified value is to be associated
- * @param val value to be associated with the specified key
- * @return the previous value associated with <tt>key</tt>, or
- * <tt>null</tt> if there was no mapping for <tt>key</tt>
- * @throws NullPointerException if the specified key or value is null */
- @Override
- public TypeV put ( TypeK key, TypeV val ) { return putIfMatch( key, val, NO_MATCH_OLD); }
-
- /** Atomically, do a {@link #put} if-and-only-if the key is not mapped.
- * Useful to ensure that only a single mapping for the key exists, even if
- * many threads are trying to create the mapping in parallel.
- * @return the previous value associated with the specified key,
- * or <tt>null</tt> if there was no mapping for the key
- * @throws NullPointerException if the specified key or value is null */
- public TypeV putIfAbsent( TypeK key, TypeV val ) { return putIfMatch( key, val, TOMBSTONE ); }
-
- /** Removes the key (and its corresponding value) from this map.
- * This method does nothing if the key is not in the map.
- * @return the previous value associated with <tt>key</tt>, or
- * <tt>null</tt> if there was no mapping for <tt>key</tt>
- * @throws NullPointerException if the specified key is null */
- @Override
- public TypeV remove ( Object key ) { return putIfMatch( key,TOMBSTONE, NO_MATCH_OLD); }
-
- /** Atomically do a {@link #remove(Object)} if-and-only-if the key is mapped
- * to a value which is <code>equals</code> to the given value.
- * @throws NullPointerException if the specified key or value is null */
- public boolean remove ( Object key,Object val ) { return putIfMatch( key,TOMBSTONE, val ) == val; }
-
- /** Atomically do a <code>put(key,val)</code> if-and-only-if the key is
- * mapped to some value already.
- * @throws NullPointerException if the specified key or value is null */
- public TypeV replace ( TypeK key, TypeV val ) { return putIfMatch( key, val,MATCH_ANY ); }
-
- /** Atomically do a <code>put(key,newValue)</code> if-and-only-if the key is
- * mapped a value which is <code>equals</code> to <code>oldValue</code>.
- * @throws NullPointerException if the specified key or value is null */
- public boolean replace ( TypeK key, TypeV oldValue, TypeV newValue ) {
- return putIfMatch( key, newValue, oldValue ) == oldValue;
- }
-
-
- // Atomically replace newVal for oldVal, returning the value that existed
- // there before. If oldVal is the returned value, then newVal was inserted,
- // otherwise not. A null oldVal means the key does not exist (only insert if
- // missing); a null newVal means to remove the key.
- @SuppressWarnings("unchecked")
- public final TypeV putIfMatchAllowNull( Object key, Object newVal, Object oldVal ) {
- if( oldVal == null ) oldVal = TOMBSTONE;
- if( newVal == null ) newVal = TOMBSTONE;
- final TypeV res = (TypeV)putIfMatch( this, _kvs, key, newVal, oldVal );
- assert !(res instanceof Prime);
- //assert res != null;
- return res == TOMBSTONE ? null : res;
- }
-
- @SuppressWarnings("unchecked")
- private final TypeV putIfMatch( Object key, Object newVal, Object oldVal ) {
- if (oldVal == null || newVal == null) throw new NullPointerException();
- final Object res = putIfMatch( this, _kvs, key, newVal, oldVal );
- assert !(res instanceof Prime);
- assert res != null;
- return res == TOMBSTONE ? null : (TypeV)res;
- }
-
-
- /** Copies all of the mappings from the specified map to this one, replacing
- * any existing mappings.
- * @param m mappings to be stored in this map */
- @Override
- public void putAll(Map<? extends TypeK, ? extends TypeV> m) {
- for (Map.Entry<? extends TypeK, ? extends TypeV> e : m.entrySet())
- put(e.getKey(), e.getValue());
- }
-
- /** Removes all of the mappings from this map. */
- @Override
- public void clear() { // Smack a new empty table down
- Object[] newkvs = new NonBlockingHashMap(MIN_SIZE)._kvs;
- while( !CAS_kvs(_kvs,newkvs) ) // Spin until the clear works
- ;
- }
- // Non-atomic clear, preserving existing large arrays
- public void clear(boolean large) { // Smack a new empty table down
- for( int i=2; i<_kvs.length; i++ ) _kvs[i]=null;
- Arrays.fill(hashes(_kvs),0);
- chm(_kvs).clear();
- }
-
- /** Returns <tt>true</tt> if this Map maps one or more keys to the specified
- * value. <em>Note</em>: This method requires a full internal traversal of the
- * hash table and is much slower than {@link #containsKey}.
- * @param val value whose presence in this map is to be tested
- * @return <tt>true</tt> if this map maps one or more keys to the specified value
- * @throws NullPointerException if the specified value is null */
- @Override
- public boolean containsValue( final Object val ) {
- if( val == null ) throw new NullPointerException();
- for( TypeV V : values() )
- if( V == val || V.equals(val) )
- return true;
- return false;
- }
-
- // This function is supposed to do something for Hashtable, and the JCK
- // tests hang until it gets called... by somebody ... for some reason,
- // any reason....
- protected void rehash() {
- }
-
- /**
- * Creates a shallow copy of this hashtable. All the structure of the
- * hashtable itself is copied, but the keys and values are not cloned.
- * This is a relatively expensive operation.
- *
- * @return a clone of the hashtable.
- */
- @SuppressWarnings("unchecked")
- @Override
- public Object clone() {
- try {
- // Must clone, to get the class right; NBHM might have been
- // extended, so it would be wrong to just make a new NBHM.
- NonBlockingHashMap<TypeK,TypeV> t = (NonBlockingHashMap<TypeK,TypeV>) super.clone();
- // But I don't have an atomic clone operation - the underlying _kvs
- // structure is undergoing rapid change. If I just clone the _kvs
- // field, the CHM in _kvs[0] won't be in sync.
- //
- // Wipe out the cloned array (it was shallow anyways).
- t.clear();
- // Now copy sanely
- for( TypeK K : keySet() ) {
- final TypeV V = get(K); // Do an official 'get'
- t.put(K,V);
- }
- return t;
- } catch (CloneNotSupportedException e) {
- // this shouldn't happen, since we are Cloneable
- throw new InternalError();
- }
- }
-
- /**
- * Returns a string representation of this map. The string representation
- * consists of a list of key-value mappings in the order returned by the
- * map's <tt>entrySet</tt> view's iterator, enclosed in braces
- * (<tt>"{}"</tt>). Adjacent mappings are separated by the characters
- * <tt>", "</tt> (comma and space). Each key-value mapping is rendered as
- * the key followed by an equals sign (<tt>"="</tt>) followed by the
- * associated value. Keys and values are converted to strings as by
- * {@link String#valueOf(Object)}.
- *
- * @return a string representation of this map
- */
- @Override
- public String toString() {
- Iterator<Entry<TypeK,TypeV>> i = entrySet().iterator();
- if( !i.hasNext())
- return "{}";
-
- StringBuilder sb = new StringBuilder();
- sb.append('{');
- for (;;) {
- Entry<TypeK,TypeV> e = i.next();
- TypeK key = e.getKey();
- TypeV value = e.getValue();
- sb.append(key == this ? "(this Map)" : key);
- sb.append('=');
- sb.append(value == this ? "(this Map)" : value);
- if( !i.hasNext())
- return sb.append('}').toString();
- sb.append(", ");
- }
- }
-
- // Get *any* valid Key. Useful for making worklists out of a hashtable - the
- // hashtable can remove dup work. Repeated calls probably return the same
- // Key, so only useful if keys are removed after calling.
- @SuppressWarnings("unchecked")
- public TypeK getKey() { return (TypeK)_getKey(_kvs); }
- private static Object _getKey(Object[] kvs) {
- for( int i=0; i<len(kvs); i++ ) {
- Object key = key(kvs,i);
- Object val = val(kvs,i);
- Object U = Prime.unbox(val);
- if( key != null && key != TOMBSTONE && // key is sane
- val != null && U != TOMBSTONE ) // val is sane
- return key;
- }
- Object[] newkvs = chm(kvs)._newkvs; // New table, if any
- return newkvs == null ? null : _getKey(newkvs);
- }
-
- // --- keyeq ---------------------------------------------------------------
- // Check for key equality. Try direct pointer compare first, then see if
- // the hashes are unequal (fast negative test) and finally do the full-on
- // 'equals' v-call.
- private static boolean keyeq( Object K, Object key, int[] hashes, int hash, int fullhash ) {
- return
- K==key || // Either keys match exactly OR
- // hash exists and matches? hash can be zero during the install of a
- // new key/value pair.
- ((hashes[hash] == 0 || hashes[hash] == fullhash) &&
- // Do not call the users' "equals()" call with a Tombstone, as this can
- // surprise poorly written "equals()" calls that throw exceptions
- // instead of simply returning false.
- K != TOMBSTONE && // Do not call users' equals call with a Tombstone
- // Do the match the hard way - with the users' key being the loop-
- // invariant "this" pointer. I could have flipped the order of
- // operands (since equals is commutative), but I'm making mega-morphic
- // v-calls in a reprobing loop and nailing down the 'this' argument
- // gives both the JIT and the hardware a chance to prefetch the call target.
- key.equals(K)); // Finally do the hard match
- }
-
- // --- get -----------------------------------------------------------------
- /** Returns the value to which the specified key is mapped, or {@code null}
- * if this map contains no mapping for the key.
- * <p>More formally, if this map contains a mapping from a key {@code k} to
- * a value {@code v} such that {@code key.equals(k)}, then this method
- * returns {@code v}; otherwise it returns {@code null}. (There can be at
- * most one such mapping.)
- * @throws NullPointerException if the specified key is null */
- // Never returns a Prime nor a Tombstone.
- @SuppressWarnings("unchecked")
- @Override
- public TypeV get( Object key ) {
- final Object V = get_impl(this,_kvs,key);
- assert !(V instanceof Prime); // Never return a Prime
- assert V != TOMBSTONE;
- return (TypeV)V;
- }
-
- private static Object get_impl( final NonBlockingHashMap topmap, final Object[] kvs, final Object key ) {
- final int fullhash= hash (key); // throws NullPointerException if key is null
- final int len = len (kvs); // Count of key/value pairs, reads kvs.length
- final CHM chm = chm (kvs); // The CHM, for a volatile read below; reads slot 0 of kvs
- final int[] hashes=hashes(kvs); // The memoized hashes; reads slot 1 of kvs
-
- int idx = fullhash & (len-1); // First key hash
-
- // Main spin/reprobe loop, looking for a Key hit
- int reprobe_cnt=0;
- try {
- while( true ) {
- // Probe table. Each read of 'val' probably misses in cache in a big
- // table; hopefully the read of 'key' then hits in cache.
- final Object K = key(kvs,idx); // Get key before volatile read, could be null
- final Object V = val(kvs,idx); // Get value before volatile read, could be null or Tombstone or Prime
- if( K == null ) return null; // A clear miss
-
- // We need a volatile-read here to preserve happens-before semantics on
- // newly inserted Keys. If the Key body was written just before inserting
- // into the table a Key-compare here might read the uninitialized Key body.
- // Annoyingly this means we have to volatile-read before EACH key compare.
- // .
- // We also need a volatile-read between reading a newly inserted Value
- // and returning the Value (so the user might end up reading the stale
- // Value contents). Same problem as with keys - and the one volatile
- // read covers both.
- final Object[] newkvs = chm._newkvs; // VOLATILE READ before key compare
-
- // Key-compare
- if( keyeq(K,key,hashes,idx,fullhash) ) {
- // Key hit! Check for no table-copy-in-progress
- if( !(V instanceof Prime) ) // No copy?
- return (V == TOMBSTONE) ? null : V; // Return the value
- // Key hit - but slot is (possibly partially) copied to the new table.
- // Finish the copy & retry in the new table.
- return get_impl(topmap,chm.copy_slot_and_check(topmap,kvs,idx,key),key); // Retry in the new table
- }
- // get and put must have the same key lookup logic! But only 'put'
- // needs to force a table-resize for a too-long key-reprobe sequence.
- // Check for too-many-reprobes on get - and flip to the new table.
- if( ++reprobe_cnt >= reprobe_limit(len) || // too many probes
- K == TOMBSTONE ) // found a TOMBSTONE key, means no more keys in this table
- return newkvs == null ? null : get_impl(topmap,topmap.help_copy(newkvs),key); // Retry in the new table
-
- idx = (idx+((fullhash>>16)|1))&(len-1); // Divergent reprobe chain
- }
- } finally {
- topmap.record(reprobe_cnt);
- }
- }
-
- // --- getk -----------------------------------------------------------------
- /** Returns the Key to which the specified key is mapped, or {@code null}
- * if this map contains no mapping for the key.
- * @throws NullPointerException if the specified key is null */
- // Never returns a Prime nor a Tombstone.
- @SuppressWarnings("unchecked")
- public TypeK getk( TypeK key ) {
- return (TypeK)getk_impl(this,_kvs,key);
- }
-
- private static final Object getk_impl( final NonBlockingHashMap topmap, final Object[] kvs, final Object key ) {
- final int fullhash= hash (key); // throws NullPointerException if key is null
- final int len = len (kvs); // Count of key/value pairs, reads kvs.length
- final CHM chm = chm (kvs); // The CHM, for a volatile read below; reads slot 0 of kvs
- final int[] hashes=hashes(kvs); // The memoized hashes; reads slot 1 of kvs
-
- int idx = fullhash & (len-1); // First key hash
-
- // Main spin/reprobe loop, looking for a Key hit
- int reprobe_cnt=0;
- try {
- while( true ) {
- // Probe table.
- final Object K = key(kvs,idx); // Get key before volatile read, could be null
- if( K == null ) return null; // A clear miss
-
- // We need a volatile-read here to preserve happens-before semantics on
- // newly inserted Keys. If the Key body was written just before inserting
- // into the table a Key-compare here might read the uninitialized Key body.
- // Annoyingly this means we have to volatile-read before EACH key compare.
- // .
- // We also need a volatile-read between reading a newly inserted Value
- // and returning the Value (so the user might end up reading the stale
- // Value contents). Same problem as with keys - and the one volatile
- // read covers both.
- final Object[] newkvs = chm._newkvs; // VOLATILE READ before key compare
-
- // Key-compare
- if( keyeq(K,key,hashes,idx,fullhash) )
- return K; // Return existing Key!
-
- // get and put must have the same key lookup logic! But only 'put'
- // needs to force a table-resize for a too-long key-reprobe sequence.
- // Check for too-many-reprobes on get - and flip to the new table.
- if( ++reprobe_cnt >= reprobe_limit(len) || // too many probes
- K == TOMBSTONE ) { // found a TOMBSTONE key, means no more keys in this table
- return newkvs == null ? null : getk_impl(topmap,topmap.help_copy(newkvs),key); // Retry in the new table
- }
-
- idx = (idx+((fullhash>>16)|1))&(len-1); // Divergent reprobe chain
- }
- } finally {
- topmap.record(reprobe_cnt);
- }
- }
-
- // --- putIfMatch ---------------------------------------------------------
- // Put, Remove, PutIfAbsent, etc. Return the old value. If the returned
- // value is equal to expVal (or expVal is NO_MATCH_OLD) then the put can be
- // assumed to work (although might have been immediately overwritten). Only
- // the path through copy_slot passes in an expected value of null, and
- // putIfMatch only returns a null if passed in an expected null.
- static volatile int DUMMY_VOLATILE;
- private static final Object putIfMatch( final NonBlockingHashMap topmap, final Object[] kvs, final Object key, final Object putval, final Object expVal ) {
- assert putval != null;
- assert !(putval instanceof Prime);
- assert !(expVal instanceof Prime);
- final int fullhash = hash (key); // throws NullPointerException if key null
- final int len = len (kvs); // Count of key/value pairs, reads kvs.length
- final CHM chm = chm (kvs); // Reads kvs[0]
- final int[] hashes = hashes(kvs); // Reads kvs[1], read before kvs[0]
- int idx = fullhash & (len-1);
-
- // ---
- // Key-Claim stanza: spin till we can claim a Key (or force a resizing).
- int reprobe_cnt=0;
- Object K=null, V=null;
- Object[] newkvs=null;
- try {
- while( true ) { // Spin till we get a Key slot
- V = val(kvs,idx); // Get old value (before volatile read below!)
- K = key(kvs,idx); // Get current key
- if( K == null ) { // Slot is free?
- // Found an empty Key slot - which means this Key has never been in
- // this table. No need to put a Tombstone - the Key is not here!
- if( putval == TOMBSTONE ) return putval; // Not-now & never-been in this table
- if( expVal == MATCH_ANY ) return null; // Will not match, even after K inserts
- // Claim the null key-slot
- if( CAS_key(kvs,idx, null, key ) ) { // Claim slot for Key
- chm._slots.add(1); // Raise key-slots-used count
- hashes[idx] = fullhash; // Memoize fullhash
- break; // Got it!
- }
- // CAS to claim the key-slot failed.
- //
- // This re-read of the Key points out an annoying short-coming of Java
- // CAS. Most hardware CAS's report back the existing value - so that
- // if you fail you have a *witness* - the value which caused the CAS to
- // fail. The Java API turns this into a boolean destroying the
- // witness. Re-reading does not recover the witness because another
- // thread can write over the memory after the CAS. Hence we can be in
- // the unfortunate situation of having a CAS fail *for cause* but
- // having that cause removed by a later store. This turns a
- // non-spurious-failure CAS (such as Azul has) into one that can
- // apparently spuriously fail - and we avoid apparent spurious failure
- // by not allowing Keys to ever change.
-
- // Volatile read, to force loads of K to retry despite JIT, otherwise
- // it is legal to e.g. haul the load of "K = key(kvs,idx);" outside of
- // this loop (since failed CAS ops have no memory ordering semantics).
- int dummy = DUMMY_VOLATILE;
- continue;
- }
- // Key slot was not null, there exists a Key here
-
- // We need a volatile-read here to preserve happens-before semantics on
- // newly inserted Keys. If the Key body was written just before inserting
- // into the table a Key-compare here might read the uninitialized Key body.
- // Annoyingly this means we have to volatile-read before EACH key compare.
- newkvs = chm._newkvs; // VOLATILE READ before key compare
-
- if( keyeq(K,key,hashes,idx,fullhash) )
- break; // Got it!
-
- // get and put must have the same key lookup logic! Lest 'get' give
- // up looking too soon.
- //topmap._reprobes.add(1);
- if( ++reprobe_cnt >= reprobe_limit(len) || // too many probes or
- K == TOMBSTONE ) { // found a TOMBSTONE key, means no more keys
- // We simply must have a new table to do a 'put'. At this point a
- // 'get' will also go to the new table (if any). We do not need
- // to claim a key slot (indeed, we cannot find a free one to claim!).
- newkvs = chm.resize(topmap,kvs);
- if( expVal != null ) topmap.help_copy(newkvs); // help along an existing copy
- return putIfMatch(topmap,newkvs,key,putval,expVal);
- }
-
- idx = (idx+((fullhash>>16)|1))&(len-1); // Divergent reprobe chain
- } // End of spinning till we get a Key slot
- } finally {
- topmap.record(reprobe_cnt);
- }
-
- // ---
- // Found the proper Key slot, now update the matching Value slot. We
- // never put a null, so Value slots monotonically move from null to
- // not-null (deleted Values use Tombstone). Thus if 'V' is null we
- // fail this fast cutout and fall into the check for table-full.
- if( putval == V ) return V; // Fast cutout for no-change
-
- // See if we want to move to a new table (to avoid high average re-probe
- // counts). We only check on the initial set of a Value from null to
- // not-null (i.e., once per key-insert). Of course we got a 'free' check
- // of newkvs once per key-compare (not really free, but paid-for by the
- // time we get here).
- if( newkvs == null && // New table-copy already spotted?
- // Once per fresh key-insert check the hard way
- ((V == null && chm.tableFull(reprobe_cnt,len)) ||
- // Or we found a Prime, but the JMM allowed reordering such that we
- // did not spot the new table (very rare race here: the writing
- // thread did a CAS of _newkvs then a store of a Prime. This thread
- // reads the Prime, then reads _newkvs - but the read of Prime was so
- // delayed (or the read of _newkvs was so accelerated) that they
- // swapped and we still read a null _newkvs. The resize call below
- // will do a CAS on _newkvs forcing the read.
- V instanceof Prime) )
- newkvs = chm.resize(topmap,kvs); // Force the new table copy to start
- // See if we are moving to a new table.
- // If so, copy our slot and retry in the new table.
- if( newkvs != null )
- return putIfMatch(topmap,chm.copy_slot_and_check(topmap,kvs,idx,expVal),key,putval,expVal);
-
- // ---
- // We are finally prepared to update the existing table
- assert !(V instanceof Prime);
-
- // Must match old, and we do not? Then bail out now. Note that either V
- // or expVal might be TOMBSTONE. Also V can be null, if we've never
- // inserted a value before. expVal can be null if we are called from
- // copy_slot.
-
- if( expVal != NO_MATCH_OLD && // Do we care about expected-Value at all?
- V != expVal && // No instant match already?
- (expVal != MATCH_ANY || V == TOMBSTONE || V == null) &&
- !(V==null && expVal == TOMBSTONE) && // Match on null/TOMBSTONE combo
- (expVal == null || !expVal.equals(V)) ) // Expensive equals check at the last
- return V; // Do not update!
-
- // Actually change the Value in the Key,Value pair
- if( CAS_val(kvs, idx, V, putval ) ) {
- // CAS succeeded - we did the update!
- // Both normal put's and table-copy calls putIfMatch, but table-copy
- // does not (effectively) increase the number of live k/v pairs.
- if( expVal != null ) {
- // Adjust sizes - a striped counter
- if( (V == null || V == TOMBSTONE) && putval != TOMBSTONE ) chm._size.add( 1);
- if( !(V == null || V == TOMBSTONE) && putval == TOMBSTONE ) chm._size.add(-1);
- }
- } else { // Else CAS failed
- V = val(kvs,idx); // Get new value
- // If a Prime'd value got installed, we need to re-run the put on the
- // new table. Otherwise we lost the CAS to another racing put.
- // Simply retry from the start.
- if( V instanceof Prime )
- return putIfMatch(topmap,chm.copy_slot_and_check(topmap,kvs,idx,expVal),key,putval,expVal);
- }
- // Win or lose the CAS, we are done. If we won then we know the update
- // happened as expected. If we lost, it means "we won but another thread
- // immediately stomped our update with no chance of a reader reading".
- return (V==null && expVal!=null) ? TOMBSTONE : V;
- }
-
- // --- help_copy ---------------------------------------------------------
- // Help along an existing resize operation. This is just a fast cut-out
- // wrapper, to encourage inlining for the fast no-copy-in-progress case. We
- // always help the top-most table copy, even if there are nested table
- // copies in progress.
- private final Object[] help_copy( Object[] helper ) {
- // Read the top-level KVS only once. We'll try to help this copy along,
- // even if it gets promoted out from under us (i.e., the copy completes
- // and another KVS becomes the top-level copy).
- Object[] topkvs = _kvs;
- CHM topchm = chm(topkvs);
- if( topchm._newkvs == null ) return helper; // No copy in-progress
- topchm.help_copy_impl(this,topkvs,false);
- return helper;
- }
-
-
- // --- CHM -----------------------------------------------------------------
- // The control structure for the NonBlockingHashMap
- private static final class CHM<TypeK,TypeV> {
- // Size in active K,V pairs
- private final ConcurrentAutoTable _size;
- public int size () { return (int)_size.get(); }
-
- // ---
- // These next 2 fields are used in the resizing heuristics, to judge when
- // it is time to resize or copy the table. Slots is a count of used-up
- // key slots, and when it nears a large fraction of the table we probably
- // end up reprobing too much. Last-resize-milli is the time since the
- // last resize; if we are running back-to-back resizes without growing
- // (because there are only a few live keys but many slots full of dead
- // keys) then we need a larger table to cut down on the churn.
-
- // Count of used slots, to tell when table is full of dead unusable slots
- private final ConcurrentAutoTable _slots;
- public int slots() { return (int)_slots.get(); }
-
- // ---
- // New mappings, used during resizing.
- // The 'new KVs' array - created during a resize operation. This
- // represents the new table being copied from the old one. It's the
- // volatile variable that is read as we cross from one table to the next,
- // to get the required memory orderings. It monotonically transits from
- // null to set (once).
- volatile Object[] _newkvs;
- private final AtomicReferenceFieldUpdater<CHM,Object[]> _newkvsUpdater =
- AtomicReferenceFieldUpdater.newUpdater(CHM.class,Object[].class, "_newkvs");
- // Set the _next field if we can.
- boolean CAS_newkvs( Object[] newkvs ) {
- while( _newkvs == null )
- if( _newkvsUpdater.compareAndSet(this,null,newkvs) )
- return true;
- return false;
- }
-
- // Sometimes many threads race to create a new very large table. Only 1
- // wins the race, but the losers all allocate a junk large table with
- // hefty allocation costs. Attempt to control the overkill here by
- // throttling attempts to create a new table. I cannot really block here
- // (lest I lose the non-blocking property) but late-arriving threads can
- // give the initial resizing thread a little time to allocate the initial
- // new table. The Right Long Term Fix here is to use array-lets and
- // incrementally create the new very large array. In C I'd make the array
- // with malloc (which would mmap under the hood) which would only eat
- // virtual-address and not real memory - and after Somebody wins then we
- // could in parallel initialize the array. Java does not allow
- // un-initialized array creation (especially of ref arrays!).
- volatile long _resizers; // count of threads attempting an initial resize
- private static final AtomicLongFieldUpdater<CHM> _resizerUpdater =
- AtomicLongFieldUpdater.newUpdater(CHM.class, "_resizers");
-
- // ---
- // Simple constructor
- CHM( ConcurrentAutoTable size ) {
- _size = size;
- _slots= new ConcurrentAutoTable();
- }
-
- // Fast, not-atomic clear
- public void clear() { _size.clear(); _slots.clear(); }
-
- // --- tableFull ---------------------------------------------------------
- // Heuristic to decide if this table is too full, and we should start a
- // new table. Note that if a 'get' call has reprobed too many times and
- // decided the table must be full, then always the estimate_sum must be
- // high and we must report the table is full. If we do not, then we might
- // end up deciding that the table is not full and inserting into the
- // current table, while a 'get' has decided the same key cannot be in this
- // table because of too many reprobes. The invariant is:
- // slots.estimate_sum >= max_reprobe_cnt >= reprobe_limit(len)
- private final boolean tableFull( int reprobe_cnt, int len ) {
- return
- // Do the cheap check first: we allow some number of reprobes always
- reprobe_cnt >= REPROBE_LIMIT &&
- (reprobe_cnt >= reprobe_limit(len) ||
- // More expensive check: see if the table is > 1/2 full.
- _slots.estimate_get() >= (len>>1));
- }
-
- // --- resize ------------------------------------------------------------
- // Resizing after too many probes. "How Big???" heuristics are here.
- // Callers will (not this routine) will 'help_copy' any in-progress copy.
- // Since this routine has a fast cutout for copy-already-started, callers
- // MUST 'help_copy' lest we have a path which forever runs through
- // 'resize' only to discover a copy-in-progress which never progresses.
- private final Object[] resize( NonBlockingHashMap topmap, Object[] kvs) {
- assert chm(kvs) == this;
-
- // Check for resize already in progress, probably triggered by another thread
- Object[] newkvs = _newkvs; // VOLATILE READ
- if( newkvs != null ) // See if resize is already in progress
- return newkvs; // Use the new table already
-
- // No copy in-progress, so start one. First up: compute new table size.
- int oldlen = len(kvs); // Old count of K,V pairs allowed
- int sz = size(); // Get current table count of active K,V pairs
- int newsz = sz; // First size estimate
-
- // Heuristic to determine new size. We expect plenty of dead-slots-with-keys
- // and we need some decent padding to avoid endless reprobing.
- if( sz >= (oldlen>>2) ) { // If we are >25% full of keys then...
- newsz = oldlen<<1; // Double size, so new table will be between 12.5% and 25% full
- // For tables less than 1M entries, if >50% full of keys then...
- // For tables more than 1M entries, if >75% full of keys then...
- if( 4L*sz >= ((oldlen>>20)!=0?3L:2L)*oldlen )
- newsz = oldlen<<2; // Double double size, so new table will be between %12.5 (18.75%) and 25% (25%)
- }
- // This heuristic in the next 2 lines leads to a much denser table
- // with a higher reprobe rate
- //if( sz >= (oldlen>>1) ) // If we are >50% full of keys then...
- // newsz = oldlen<<1; // Double size
-
- // Last (re)size operation was very recent? Then double again; slows
- // down resize operations for tables subject to a high key churn rate.
- long tm = System.currentTimeMillis();
- long q=0;
- if( newsz <= oldlen && // New table would shrink or hold steady?
- (tm <= topmap._last_resize_milli+10000 || // Recent resize (less than 10 sec ago)
- (q=_slots.estimate_get()) >= (sz<<1)) ) // 1/2 of keys are dead?
- newsz = oldlen<<1; // Double the existing size
-
- // Do not shrink, ever
- if( newsz < oldlen ) newsz = oldlen;
-
- // Convert to power-of-2
- int log2;
- for( log2=MIN_SIZE_LOG; (1<<log2) < newsz; log2++ ) ; // Compute log2 of size
- long len = ((1L << log2) << 1) + 2;
- // prevent integer overflow - limit of 2^31 elements in a Java array
- // so here, 2^30 + 2 is the largest number of elements in the hash table
- if ((int)len!=len) {
- log2 = 30;
- len = (1L << log2) + 2;
- if (sz > ((len >> 2) + (len >> 1))) throw new RuntimeException("Table is full.");
- }
-
- // Now limit the number of threads actually allocating memory to a
- // handful - lest we have 750 threads all trying to allocate a giant
- // resized array.
- long r = _resizers;
- while( !_resizerUpdater.compareAndSet(this,r,r+1) )
- r = _resizers;
- // Size calculation: 2 words (K+V) per table entry, plus a handful. We
- // guess at 64-bit pointers; 32-bit pointers screws up the size calc by
- // 2x but does not screw up the heuristic very much.
- long megs = ((((1L<<log2)<<1)+8)<<3/*word to bytes*/)>>20/*megs*/;
- if( r >= 2 && megs > 0 ) { // Already 2 guys trying; wait and see
- newkvs = _newkvs; // Between dorking around, another thread did it
- if( newkvs != null ) // See if resize is already in progress
- return newkvs; // Use the new table already
- // TODO - use a wait with timeout, so we'll wakeup as soon as the new table
- // is ready, or after the timeout in any case.
- //synchronized( this ) { wait(8*megs); } // Timeout - we always wakeup
- // For now, sleep a tad and see if the 2 guys already trying to make
- // the table actually get around to making it happen.
- try { Thread.sleep(megs); } catch( Exception e ) { }
- }
- // Last check, since the 'new' below is expensive and there is a chance
- // that another thread slipped in a new thread while we ran the heuristic.
- newkvs = _newkvs;
- if( newkvs != null ) // See if resize is already in progress
- return newkvs; // Use the new table already
-
- // Double size for K,V pairs, add 1 for CHM
- newkvs = new Object[(int)len]; // This can get expensive for big arrays
- newkvs[0] = new CHM(_size); // CHM in slot 0
- newkvs[1] = new int[1<<log2]; // hashes in slot 1
-
- // Another check after the slow allocation
- if( _newkvs != null ) // See if resize is already in progress
- return _newkvs; // Use the new table already
-
- // The new table must be CAS'd in so only 1 winner amongst duplicate
- // racing resizing threads. Extra CHM's will be GC'd.
- if( CAS_newkvs( newkvs ) ) { // NOW a resize-is-in-progress!
- //notifyAll(); // Wake up any sleepers
- //long nano = System.nanoTime();
- //System.out.println(" "+nano+" Resize from "+oldlen+" to "+(1<<log2)+" and had "+(_resizers-1)+" extras" );
- //if( System.out != null ) System.out.print("["+log2);
- topmap.rehash(); // Call for Hashtable's benefit
- } else // CAS failed?
- newkvs = _newkvs; // Reread new table
- return newkvs;
- }
-
-
- // The next part of the table to copy. It monotonically transits from zero
- // to _kvs.length. Visitors to the table can claim 'work chunks' by
- // CAS'ing this field up, then copying the indicated indices from the old
- // table to the new table. Workers are not required to finish any chunk;
- // the counter simply wraps and work is copied duplicately until somebody
- // somewhere completes the count.
- volatile long _copyIdx = 0;
- static private final AtomicLongFieldUpdater<CHM> _copyIdxUpdater =
- AtomicLongFieldUpdater.newUpdater(CHM.class, "_copyIdx");
-
- // Work-done reporting. Used to efficiently signal when we can move to
- // the new table. From 0 to len(oldkvs) refers to copying from the old
- // table to the new.
- volatile long _copyDone= 0;
- static private final AtomicLongFieldUpdater<CHM> _copyDoneUpdater =
- AtomicLongFieldUpdater.newUpdater(CHM.class, "_copyDone");
-
- // --- help_copy_impl ----------------------------------------------------
- // Help along an existing resize operation. We hope its the top-level
- // copy (it was when we started) but this CHM might have been promoted out
- // of the top position.
- private final void help_copy_impl( NonBlockingHashMap topmap, Object[] oldkvs, boolean copy_all ) {
- assert chm(oldkvs) == this;
- Object[] newkvs = _newkvs;
- assert newkvs != null; // Already checked by caller
- int oldlen = len(oldkvs); // Total amount to copy
- final int MIN_COPY_WORK = Math.min(oldlen,1024); // Limit per-thread work
-
- // ---
- int panic_start = -1;
- int copyidx=-9999; // Fool javac to think it's initialized
- while( _copyDone < oldlen ) { // Still needing to copy?
- // Carve out a chunk of work. The counter wraps around so every
- // thread eventually tries to copy every slot repeatedly.
-
- // We "panic" if we have tried TWICE to copy every slot - and it still
- // has not happened. i.e., twice some thread somewhere claimed they
- // would copy 'slot X' (by bumping _copyIdx) but they never claimed to
- // have finished (by bumping _copyDone). Our choices become limited:
- // we can wait for the work-claimers to finish (and become a blocking
- // algorithm) or do the copy work ourselves. Tiny tables with huge
- // thread counts trying to copy the table often 'panic'.
- if( panic_start == -1 ) { // No panic?
- copyidx = (int)_copyIdx;
- while( !_copyIdxUpdater.compareAndSet(this,copyidx,copyidx+MIN_COPY_WORK) )
- copyidx = (int)_copyIdx; // Re-read
- if( !(copyidx < (oldlen<<1)) ) // Panic!
- panic_start = copyidx; // Record where we started to panic-copy
- }
-
- // We now know what to copy. Try to copy.
- int workdone = 0;
- for( int i=0; i<MIN_COPY_WORK; i++ )
- if( copy_slot(topmap,(copyidx+i)&(oldlen-1),oldkvs,newkvs) ) // Made an oldtable slot go dead?
- workdone++; // Yes!
- if( workdone > 0 ) // Report work-done occasionally
- copy_check_and_promote( topmap, oldkvs, workdone );// See if we can promote
-
- copyidx += MIN_COPY_WORK;
- // Uncomment these next 2 lines to turn on incremental table-copy.
- // Otherwise this thread continues to copy until it is all done.
- if( !copy_all && panic_start == -1 ) // No panic?
- return; // Then done copying after doing MIN_COPY_WORK
- }
- // Extra promotion check, in case another thread finished all copying
- // then got stalled before promoting.
- copy_check_and_promote( topmap, oldkvs, 0 );// See if we can promote
- }
-
- // --- copy_slot_and_check -----------------------------------------------
- // Copy slot 'idx' from the old table to the new table. If this thread
- // confirmed the copy, update the counters and check for promotion.
- //
- // Returns the result of reading the volatile _newkvs, mostly as a
- // convenience to callers. We come here with 1-shot copy requests
- // typically because the caller has found a Prime, and has not yet read
- // the _newkvs volatile - which must have changed from null-to-not-null
- // before any Prime appears. So the caller needs to read the _newkvs
- // field to retry his operation in the new table, but probably has not
- // read it yet.
- private final Object[] copy_slot_and_check( NonBlockingHashMap topmap, Object[] oldkvs, int idx, Object should_help ) {
- assert chm(oldkvs) == this;
- Object[] newkvs = _newkvs; // VOLATILE READ
- // We're only here because the caller saw a Prime, which implies a
- // table-copy is in progress.
- assert newkvs != null;
- if( copy_slot(topmap,idx,oldkvs,_newkvs) ) // Copy the desired slot
- copy_check_and_promote(topmap, oldkvs, 1); // Record the slot copied
- // Generically help along any copy (except if called recursively from a helper)
- return (should_help == null) ? newkvs : topmap.help_copy(newkvs);
- }
-
- // --- copy_check_and_promote --------------------------------------------
- private final void copy_check_and_promote( NonBlockingHashMap topmap, Object[] oldkvs, int workdone ) {
- assert chm(oldkvs) == this;
- int oldlen = len(oldkvs);
- // We made a slot unusable and so did some of the needed copy work
- long copyDone = _copyDone;
- assert (copyDone+workdone) <= oldlen;
- if( workdone > 0 ) {
- while( !_copyDoneUpdater.compareAndSet(this,copyDone,copyDone+workdone) ) {
- copyDone = _copyDone; // Reload, retry
- assert (copyDone+workdone) <= oldlen;
- }
- //if( (10*copyDone/oldlen) != (10*(copyDone+workdone)/oldlen) )
- //System.out.print(" "+(copyDone+workdone)*100/oldlen+"%"+"_"+(_copyIdx*100/oldlen)+"%");
- }
-
- // Check for copy being ALL done, and promote. Note that we might have
- // nested in-progress copies and manage to finish a nested copy before
- // finishing the top-level copy. We only promote top-level copies.
- if( copyDone+workdone == oldlen && // Ready to promote this table?
- topmap._kvs == oldkvs && // Looking at the top-level table?
- // Attempt to promote
- topmap.CAS_kvs(oldkvs,_newkvs) ) {
- topmap._last_resize_milli = System.currentTimeMillis(); // Record resize time for next check
- //long nano = System.nanoTime();
- //System.out.println(" "+nano+" Promote table to "+len(_newkvs));
- //if( System.out != null ) System.out.print("]");
- }
- }
-
- // --- copy_slot ---------------------------------------------------------
- // Copy one K/V pair from oldkvs[i] to newkvs. Returns true if we can
- // confirm that the new table guaranteed has a value for this old-table
- // slot. We need an accurate confirmed-copy count so that we know when we
- // can promote (if we promote the new table too soon, other threads may
- // 'miss' on values not-yet-copied from the old table). We don't allow
- // any direct updates on the new table, unless they first happened to the
- // old table - so that any transition in the new table from null to
- // not-null must have been from a copy_slot (or other old-table overwrite)
- // and not from a thread directly writing in the new table. Thus we can
- // count null-to-not-null transitions in the new table.
- private boolean copy_slot( NonBlockingHashMap topmap, int idx, Object[] oldkvs, Object[] newkvs ) {
- // Blindly set the key slot from null to TOMBSTONE, to eagerly stop
- // fresh put's from inserting new values in the old table when the old
- // table is mid-resize. We don't need to act on the results here,
- // because our correctness stems from box'ing the Value field. Slamming
- // the Key field is a minor speed optimization.
- Object key;
- while( (key=key(oldkvs,idx)) == null )
- CAS_key(oldkvs,idx, null, TOMBSTONE);
-
- // ---
- // Prevent new values from appearing in the old table.
- // Box what we see in the old table, to prevent further updates.
- Object oldval = val(oldkvs,idx); // Read OLD table
- while( !(oldval instanceof Prime) ) {
- final Prime box = (oldval == null || oldval == TOMBSTONE) ? TOMBPRIME : new Prime(oldval);
- if( CAS_val(oldkvs,idx,oldval,box) ) { // CAS down a box'd version of oldval
- // If we made the Value slot hold a TOMBPRIME, then we both
- // prevented further updates here but also the (absent)
- // oldval is vacuously available in the new table. We
- // return with true here: any thread looking for a value for
- // this key can correctly go straight to the new table and
- // skip looking in the old table.
- if( box == TOMBPRIME )
- return true;
- // Otherwise we boxed something, but it still needs to be
- // copied into the new table.
- oldval = box; // Record updated oldval
- break; // Break loop; oldval is now boxed by us
- }
- oldval = val(oldkvs,idx); // Else try, try again
- }
- if( oldval == TOMBPRIME ) return false; // Copy already complete here!
-
- // ---
- // Copy the value into the new table, but only if we overwrite a null.
- // If another value is already in the new table, then somebody else
- // wrote something there and that write is happens-after any value that
- // appears in the old table. If putIfMatch does not find a null in the
- // new table - somebody else should have recorded the null-not_null
- // transition in this copy.
- Object old_unboxed = ((Prime)oldval)._V;
- assert old_unboxed != TOMBSTONE;
- putIfMatch(topmap, newkvs, key, old_unboxed, null);
-
- // ---
- // Finally, now that any old value is exposed in the new table, we can
- // forever hide the old-table value by slapping a TOMBPRIME down. This
- // will stop other threads from uselessly attempting to copy this slot
- // (i.e., it's a speed optimization not a correctness issue).
- while( oldval != TOMBPRIME && !CAS_val(oldkvs,idx,oldval,TOMBPRIME) )
- oldval = val(oldkvs,idx);
-
- return true;
- } // end copy_slot
- } // End of CHM
-
-
- // --- Snapshot ------------------------------------------------------------
- // The main class for iterating over the NBHM. It "snapshots" a clean
- // view of the K/V array.
- private class SnapshotV implements Iterator<TypeV>, Enumeration<TypeV> {
- final Object[] _sskvs;
- public SnapshotV() {
- while( true ) { // Verify no table-copy-in-progress
- Object[] topkvs = _kvs;
- CHM topchm = chm(topkvs);
- if( topchm._newkvs == null ) { // No table-copy-in-progress
- // The "linearization point" for the iteration. Every key in this
- // table will be visited, but keys added later might be skipped or
- // even be added to a following table (also not iterated over).
- _sskvs = topkvs;
- break;
- }
- // Table copy in-progress - so we cannot get a clean iteration. We
- // must help finish the table copy before we can start iterating.
- topchm.help_copy_impl(NonBlockingHashMap.this,topkvs,true);
- }
- // Warm-up the iterator
- next();
- }
- int length() { return len(_sskvs); }
- Object key(int idx) { return NonBlockingHashMap.key(_sskvs,idx); }
- Object val(int idx) { return NonBlockingHashMap.val(_sskvs,idx); }
- private int _idx; // Varies from 0-keys.length
- private Object _nextK, _prevK; // Last 2 keys found
- private TypeV _nextV, _prevV; // Last 2 values found
- public boolean hasNext() { return _nextV != null; }
- @SuppressWarnings("unchecked")
- public TypeV next() {
- // 'next' actually knows what the next value will be - it had to
- // figure that out last go-around lest 'hasNext' report true and
- // some other thread deleted the last value. Instead, 'next'
- // spends all its effort finding the key that comes after the
- // 'next' key.
- if( _idx != 0 && _nextV == null ) throw new NoSuchElementException();
- _prevK = _nextK; // This will become the previous key
- _prevV = _nextV; // This will become the previous value
- _nextV = null; // We have no more next-key
- Object nV;
- // Attempt to set <_nextK,_nextV> to the next K,V pair.
- // _nextV is the trigger: stop searching when it is != null
- while( _idx<length() ) { // Scan array
- _nextK = key(_idx++); // Get a key that definitely is in the set (for the moment!)
- if( _nextK != null && // Found something?
- _nextK != TOMBSTONE && // Key is not TOMBSTONE
- // Keys can be deleted and not TOMBSTONE, and after deletion had
- // their innards gutted... so you cannot use them to compute their
- // own hash or 'get' a Value. So get the paired Value directly.
- (nV=val(_idx-1)) != null && // Get value
- nV != TOMBSTONE ) // Value is not TOMBSTONE
- { _nextV = (TypeV)nV; break; } // Got it! _nextK/_nextV is a valid Key/Value
- } // Else keep scanning
- return _prevV; // Return current value.
- }
- public void remove() {
- if( _prevV == null ) throw new IllegalStateException();
- putIfMatch( NonBlockingHashMap.this, _sskvs, _prevK, TOMBSTONE, _prevV );
- _prevV = null;
- }
-
- public TypeV nextElement() { return next(); }
- public boolean hasMoreElements() { return hasNext(); }
- }
- public Object[] raw_array() { return new SnapshotV()._sskvs; }
-
- /** Returns an enumeration of the values in this table.
- * @return an enumeration of the values in this table
- * @see #values() */
- public Enumeration<TypeV> elements() { return new SnapshotV(); }
-
- // --- values --------------------------------------------------------------
- /** Returns a {@link Collection} view of the values contained in this map.
- * The collection is backed by the map, so changes to the map are reflected
- * in the collection, and vice-versa. The collection supports element
- * removal, which removes the corresponding mapping from this map, via the
- * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
- * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
- * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
- *
- * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator that
- * will never throw {@link ConcurrentModificationException}, and guarantees
- * to traverse elements as they existed upon construction of the iterator,
- * and may (but is not guaranteed to) reflect any modifications subsequent
- * to construction. */
- @Override
- public Collection<TypeV> values() {
- return new AbstractCollection<TypeV>() {
- @Override public void clear ( ) { NonBlockingHashMap.this.clear ( ); }
- @Override public int size ( ) { return NonBlockingHashMap.this.size ( ); }
- @Override public boolean contains( Object v ) { return NonBlockingHashMap.this.containsValue(v); }
- @Override public Iterator<TypeV> iterator() { return new SnapshotV(); }
- };
- }
-
- // --- keySet --------------------------------------------------------------
- @SuppressWarnings("unchecked")
- private class SnapshotK implements Iterator<TypeK>, Enumeration<TypeK> {
- final SnapshotV _ss;
- public SnapshotK() { _ss = new SnapshotV(); }
- public void remove() { _ss.remove(); }
- public TypeK next() { _ss.next(); return (TypeK)_ss._prevK; }
- public boolean hasNext() { return _ss.hasNext(); }
- public TypeK nextElement() { return next(); }
- public boolean hasMoreElements() { return hasNext(); }
- }
-
- /** Returns an enumeration of the keys in this table.
- * @return an enumeration of the keys in this table
- * @see #keySet() */
- public Enumeration<TypeK> keys() { return new SnapshotK(); }
-
- /** Returns a {@link Set} view of the keys contained in this map. The set
- * is backed by the map, so changes to the map are reflected in the set,
- * and vice-versa. The set supports element removal, which removes the
- * corresponding mapping from this map, via the <tt>Iterator.remove</tt>,
- * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
- * <tt>clear</tt> operations. It does not support the <tt>add</tt> or
- * <tt>addAll</tt> operations.
- *
- * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator that
- * will never throw {@link ConcurrentModificationException}, and guarantees
- * to traverse elements as they existed upon construction of the iterator,
- * and may (but is not guaranteed to) reflect any modifications subsequent
- * to construction. */
- @Override
- public Set<TypeK> keySet() {
- return new AbstractSet<TypeK> () {
- @Override public void clear ( ) { NonBlockingHashMap.this.clear ( ); }
- @Override public int size ( ) { return NonBlockingHashMap.this.size ( ); }
- @Override public boolean contains( Object k ) { return NonBlockingHashMap.this.containsKey(k); }
- @Override public boolean remove ( Object k ) { return NonBlockingHashMap.this.remove (k) != null; }
- @Override public Iterator<TypeK> iterator() { return new SnapshotK(); }
- };
- }
-
-
- // --- entrySet ------------------------------------------------------------
- // Warning: Each call to 'next' in this iterator constructs a new NBHMEntry.
- private class NBHMEntry extends AbstractEntry<TypeK,TypeV> {
- NBHMEntry( final TypeK k, final TypeV v ) { super(k,v); }
- public TypeV setValue(final TypeV val) {
- if( val == null ) throw new NullPointerException();
- _val = val;
- return put(_key, val);
- }
- }
-
- @SuppressWarnings("unchecked")
- private class SnapshotE implements Iterator<Map.Entry<TypeK,TypeV>> {
- final SnapshotV _ss;
- public SnapshotE() { _ss = new SnapshotV(); }
- public void remove() { _ss.remove(); }
- public Map.Entry<TypeK,TypeV> next() { _ss.next(); return new NBHMEntry((TypeK)_ss._prevK,_ss._prevV); }
- public boolean hasNext() { return _ss.hasNext(); }
- }
-
- /** Returns a {@link Set} view of the mappings contained in this map. The
- * set is backed by the map, so changes to the map are reflected in the
- * set, and vice-versa. The set supports element removal, which removes
- * the corresponding mapping from the map, via the
- * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, <tt>removeAll</tt>,
- * <tt>retainAll</tt>, and <tt>clear</tt> operations. It does not support
- * the <tt>add</tt> or <tt>addAll</tt> operations.
- *
- * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator
- * that will never throw {@link ConcurrentModificationException},
- * and guarantees to traverse elements as they existed upon
- * construction of the iterator, and may (but is not guaranteed to)
- * reflect any modifications subsequent to construction.
- *
- * <p><strong>Warning:</strong> the iterator associated with this Set
- * requires the creation of {@link java.util.Map.Entry} objects with each
- * iteration. The {@link NonBlockingHashMap} does not normally create or
- * using {@link java.util.Map.Entry} objects so they will be created soley
- * to support this iteration. Iterating using {@link #keySet} or {@link
- * #values} will be more efficient.
- */
- @Override
- public Set<Map.Entry<TypeK,TypeV>> entrySet() {
- return new AbstractSet<Map.Entry<TypeK,TypeV>>() {
- @Override public void clear ( ) { NonBlockingHashMap.this.clear( ); }
- @Override public int size ( ) { return NonBlockingHashMap.this.size ( ); }
- @Override public boolean remove( final Object o ) {
- if( !(o instanceof Map.Entry)) return false;
- final Map.Entry<?,?> e = (Map.Entry<?,?>)o;
- return NonBlockingHashMap.this.remove(e.getKey(), e.getValue());
- }
- @Override public boolean contains(final Object o) {
- if( !(o instanceof Map.Entry)) return false;
- final Map.Entry<?,?> e = (Map.Entry<?,?>)o;
- TypeV v = get(e.getKey());
- return v.equals(e.getValue());
- }
- @Override public Iterator<Map.Entry<TypeK,TypeV>> iterator() { return new SnapshotE(); }
- };
- }
-
- // --- writeObject -------------------------------------------------------
- // Write a NBHM to a stream
- private void writeObject(java.io.ObjectOutputStream s) throws IOException {
- s.defaultWriteObject(); // Nothing to write
- for( Object K : keySet() ) {
- final Object V = get(K); // Do an official 'get'
- s.writeObject(K); // Write the <TypeK,TypeV> pair
- s.writeObject(V);
- }
- s.writeObject(null); // Sentinel to indicate end-of-data
- s.writeObject(null);
- }
-
- // --- readObject --------------------------------------------------------
- // Read a CHM from a stream
- @SuppressWarnings("unchecked")
- private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
- s.defaultReadObject(); // Read nothing
- initialize(MIN_SIZE);
- for(;;) {
- final TypeK K = (TypeK) s.readObject();
- final TypeV V = (TypeV) s.readObject();
- if( K == null ) break;
- put(K,V); // Insert with an offical put
- }
- }
-
-} // End NonBlockingHashMap class
diff --git a/src/main/java/com/seaofnodes/util/NonBlockingHashMapLong.java b/src/main/java/com/seaofnodes/util/NonBlockingHashMapLong.java
deleted file mode 100644
index 6ab2b5ee..00000000
--- a/src/main/java/com/seaofnodes/util/NonBlockingHashMapLong.java
+++ /dev/null
@@ -1,1367 +0,0 @@
-/**
- * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Contributor(s):
- *
- * The Initial Developer of the Original Software is Cliff Click.
- *
- * This file is part of the Sea Of Nodes Simple Programming language
- * implementation. See https://github.com/SeaOfNodes/Simple
- *
- * The contents of this file are subject to the terms of the
- * Apache License Version 2 (the "APL"). You may not use this
- * file except in compliance with the License. A copy of the
- * APL may be obtained from:
- * http://www.apache.org/licenses/LICENSE-2.0
- */
-package com.seaofnodes.util;
-
-import java.io.IOException;
-import java.io.Serializable;
-import java.util.*;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicLongFieldUpdater;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-
-import static com.seaofnodes.util.UtilUnsafe.UNSAFE;
-import static com.seaofnodes.util.UtilUnsafe.fieldOffset;
-
-
-/**
- * A lock-free alternate implementation of {@link java.util.concurrent.ConcurrentHashMap}
- * with <strong>primitive long keys</strong>, better scaling properties and
- * generally lower costs. The use of {@code long} keys allows for faster
- * compares and lower memory costs. The Map provides identical correctness
- * properties as ConcurrentHashMap. All operations are non-blocking and
- * multi-thread safe, including all update operations. {@link
- * NonBlockingHashMapLong} scales substatially better than {@link
- * java.util.concurrent.ConcurrentHashMap} for high update rates, even with a large
- * concurrency factor. Scaling is linear up to 768 CPUs on a 768-CPU Azul
- * box, even with 100% updates or 100% reads or any fraction in-between.
- * Linear scaling up to all cpus has been observed on a 32-way Sun US2 box,
- * 32-way Sun Niagra box, 8-way Intel box and a 4-way Power box.
- *
- * <p><strong>The main benefit of this class</strong> over using plain {@link
- * NonBlockingHashMap} with {@link Long} keys is
- * that it avoids the auto-boxing and unboxing costs. Since auto-boxing is
- * <em>automatic</em>, it is easy to accidentally cause auto-boxing and negate
- * the space and speed benefits.
- *
- * <p>This class obeys the same functional specification as {@link
- * java.util.Hashtable}, and includes versions of methods corresponding to
- * each method of <tt>Hashtable</tt>. However, even though all operations are
- * thread-safe, operations do <em>not</em> entail locking and there is
- * <em>not</em> any support for locking the entire table in a way that
- * prevents all access. This class is fully interoperable with
- * <tt>Hashtable</tt> in programs that rely on its thread safety but not on
- * its synchronization details.
- *
- * <p> Operations (including <tt>put</tt>) generally do not block, so may
- * overlap with other update operations (including other <tt>puts</tt> and
- * <tt>removes</tt>). Retrievals reflect the results of the most recently
- * <em>completed</em> update operations holding upon their onset. For
- * aggregate operations such as <tt>putAll</tt>, concurrent retrievals may
- * reflect insertion or removal of only some entries. Similarly, Iterators
- * and Enumerations return elements reflecting the state of the hash table at
- * some point at or since the creation of the iterator/enumeration. They do
- * <em>not</em> throw {@link ConcurrentModificationException}. However,
- * iterators are designed to be used by only one thread at a time.
- *
- * <p> Very full tables, or tables with high re-probe rates may trigger an
- * internal resize operation to move into a larger table. Resizing is not
- * terribly expensive, but it is not free either; during resize operations
- * table throughput may drop somewhat. All threads that visit the table
- * during a resize will 'help' the resizing but will still be allowed to
- * complete their operation before the resize is finished (i.e., a simple
- * 'get' operation on a million-entry table undergoing resizing will not need
- * to block until the entire million entries are copied).
- *
- * <p>This class and its views and iterators implement all of the
- * <em>optional</em> methods of the {@link Map} and {@link Iterator}
- * interfaces.
- *
- * <p> Like {@link Hashtable} but unlike {@link HashMap}, this class
- * does <em>not</em> allow <tt>null</tt> to be used as a value.
- *
- *
- * @since 1.5
- * @author Cliff Click
- * @param <TypeV> the type of mapped values
- */
-
-public class NonBlockingHashMapLong<TypeV>
- extends AbstractMap<Long,TypeV>
- implements ConcurrentMap<Long,TypeV>, Cloneable, Serializable {
-
- private static final long serialVersionUID = 1234123412341234124L;
-
- private static final int REPROBE_LIMIT=10; // Too many reprobes then force a table-resize
-
- // --- Bits to allow Unsafe access to arrays
- private static final int _Obase = UNSAFE.arrayBaseOffset(Object[].class);
- private static final int _Oscale = UNSAFE.arrayIndexScale(Object[].class);
- private static long rawIndex(final Object[] ary, final int idx) {
- assert idx >= 0 && idx < ary.length;
- // Note the long-math requirement, to handle arrays of more than 2^31 bytes
- // - or 2^28 - or about 268M - 8-byte pointer elements.
- return _Obase + ((long)idx * _Oscale);
- }
- private static final int _Lbase = UNSAFE.arrayBaseOffset(long[].class);
- private static final int _Lscale = UNSAFE.arrayIndexScale(long[].class);
- private static long rawIndex(final long[] ary, final int idx) {
- assert idx >= 0 && idx < ary.length;
- // Note the long-math requirement, to handle arrays of more than 2^31 bytes
- // - or 2^28 - or about 268M - 8-byte pointer elements.
- return _Lbase + ((long)idx * _Lscale);
- }
-
- // --- Bits to allow Unsafe CAS'ing of the CHM field
- private static final long _chm_offset = fieldOffset(NonBlockingHashMapLong.class, "_chm");
- private static final long _val_1_offset = fieldOffset(NonBlockingHashMapLong.class, "_val_1");
-
- private boolean CAS( final long offset, final Object old, final Object nnn ) {
- return UNSAFE.compareAndSwapObject(this, offset, old, nnn );
- }
-
- // --- Adding a 'prime' bit onto Values via wrapping with a junk wrapper class
- private static final class Prime {
- final Object _V;
- Prime( Object V ) { _V = V; }
- static Object unbox( Object V ) { return V instanceof Prime ? ((Prime)V)._V : V; }
- }
-
- // --- The Hash Table --------------------
- private transient CHM _chm;
- // This next field holds the value for Key 0 - the special key value which
- // is the initial array value, and also means: no-key-inserted-yet.
- private transient Object _val_1; // Value for Key: NO_KEY
-
- // Time since last resize
- private transient long _last_resize_milli;
-
- // Optimize for space: use a 1/2-sized table and allow more re-probes
- private final boolean _opt_for_space;
-
- // --- Minimum table size ----------------
- // Pick size 16 K/V pairs, which turns into (16*2)*4+12 = 140 bytes on a
- // standard 32-bit HotSpot, and (16*2)*8+12 = 268 bytes on 64-bit Azul.
- private static final int MIN_SIZE_LOG=2; //
- private static final int MIN_SIZE=(1<<MIN_SIZE_LOG); // Must be power of 2
-
- // --- Sentinels -------------------------
- // No-Match-Old - putIfMatch does updates only if it matches the old value,
- // and NO_MATCH_OLD basically counts as a wildcard match.
- private static final Object NO_MATCH_OLD = new Object(); // Sentinel
- // Match-Any-not-null - putIfMatch does updates only if it find a real old
- // value.
- private static final Object MATCH_ANY = new Object(); // Sentinel
- // This K/V pair has been deleted (but the Key slot is forever claimed).
- // The same Key can be reinserted with a new value later.
- private static final Object TOMBSTONE = new Object();
- // Prime'd or box'd version of TOMBSTONE. This K/V pair was deleted, then a
- // table resize started. The K/V pair has been marked so that no new
- // updates can happen to the old table (and since the K/V pair was deleted
- // nothing was copied to the new table).
- private static final Prime TOMBPRIME = new Prime(TOMBSTONE);
-
- // I exclude 1 long from the 2^64 possibilities, and test for it before
- // entering the main array. The NO_KEY value must be zero, the initial
- // value set by Java before it hands me the array.
- private static final long NO_KEY = 0L;
-
- // --- dump ----------------------------------------------------------------
- /** Verbose printout of table internals, useful for debugging. */
- public final void print() {
- System.out.println("=========");
- print_impl(-99,NO_KEY,_val_1);
- _chm.print();
- System.out.println("=========");
- }
- private static void print_impl(final int i, final long K, final Object V) {
- String p = (V instanceof Prime) ? "prime_" : "";
- Object V2 = Prime.unbox(V);
- String VS = (V2 == TOMBSTONE) ? "tombstone" : V2.toString();
- System.out.println("["+i+"]=("+K+","+p+VS+")");
- }
-
- private void print2() {
- System.out.println("=========");
- print2_impl(-99,NO_KEY,_val_1);
- _chm.print();
- System.out.println("=========");
- }
- private static void print2_impl(final int i, final long K, final Object V) {
- if( V != null && Prime.unbox(V) != TOMBSTONE )
- print_impl(i,K,V);
- }
-
- /**
- * Returns a string representation of this map. The string representation
- * consists of a list of key-value mappings in the order returned by the
- * map's <tt>entrySet</tt> view's iterator, enclosed in braces
- * (<tt>"{}"</tt>). Adjacent mappings are separated by the characters
- * <tt>", "</tt> (comma and space). Each key-value mapping is rendered as
- * the key followed by an equals sign (<tt>"="</tt>) followed by the
- * associated value. Keys and values are converted to strings as by
- * {@link String#valueOf(Object)}.
- *
- * @return a string representation of this map
- */
- @Override
- public String toString() {
- IteratorLong iter = new IteratorLong();
- if( !iter.hasNext() ) return "{}";
- SB sb = new SB("{");
- for( long key=iter.next(); iter.hasNext(); key=iter.next() ) {
- TypeV val = get(key);
- sb.p(key).p('=').p(val == this ? "(this Map)" : val.toString()).p(", ");
- }
- return sb.unchar(2).p("}").toString();
- }
-
-
- // Count of reprobes
- private transient AryInt _reprobes = null;
-
- /** Get and clear the current count of reprobes. Reprobes happen on key
- * collisions, and a high reprobe rate may indicate a poor hash function or
- * weaknesses in the table resizing function.
- * @return the count of reprobes since the last call to {@link #reprobes}
- * or since the table was created. */
- public AryInt reprobes() { return _reprobes==null ? (_reprobes = new AryInt()) : _reprobes; }
- private void record( int ps ) { if( _reprobes!=null ) _record(ps); }
- private void _record( int ps ) { _reprobes.setX(ps,_reprobes.atX(ps)+1); }
-
-
- // --- reprobe_limit -----------------------------------------------------
- // Heuristic to decide if we have reprobed toooo many times. Running over
- // the reprobe limit on a 'get' call acts as a 'miss'; on a 'put' call it
- // can trigger a table resize. Several places must have exact agreement on
- // what the reprobe_limit is, so we share it here.
- private static int reprobe_limit( int len ) {
- return REPROBE_LIMIT + (len>>4);
- }
-
- // --- NonBlockingHashMapLong ----------------------------------------------
- // Constructors
-
- /** Create a new NonBlockingHashMapLong with default minimum size (currently set
- * to 8 K/V pairs or roughly 84 bytes on a standard 32-bit JVM). */
- public NonBlockingHashMapLong( ) { this(MIN_SIZE,true); }
-
- /** Create a new NonBlockingHashMapLong with initial room for the given
- * number of elements, thus avoiding internal resizing operations to reach
- * an appropriate size. Large numbers here when used with a small count of
- * elements will sacrifice space for a small amount of time gained. The
- * initial size will be rounded up internally to the next larger power of 2. */
- public NonBlockingHashMapLong( final int initial_sz ) { this(initial_sz,true); }
-
- /** Create a new NonBlockingHashMapLong, setting the space-for-speed
- * tradeoff. {@code true} optimizes for space and is the default. {@code
- * false} optimizes for speed and doubles space costs for roughly a 10%
- * speed improvement. */
- public NonBlockingHashMapLong( final boolean opt_for_space ) { this(1,opt_for_space); }
-
- /** Create a new NonBlockingHashMapLong, setting both the initial size and
- * the space-for-speed tradeoff. {@code true} optimizes for space and is
- * the default. {@code false} optimizes for speed and doubles space costs
- * for roughly a 10% speed improvement. */
- public NonBlockingHashMapLong( final int initial_sz, final boolean opt_for_space ) {
- _opt_for_space = opt_for_space;
- initialize(initial_sz);
- }
- private void initialize( final int initial_sz ) {
- if( initial_sz < 0 ) throw new IllegalArgumentException("initial_sz argument must be >= 0");
- int i; // Convert to next largest power-of-2
- for( i=MIN_SIZE_LOG; (1<<i) < initial_sz; i++ ) {/*empty*/}
- _chm = new CHM(this,new ConcurrentAutoTable(),i);
- _val_1 = TOMBSTONE; // Always as-if deleted
- _last_resize_milli = System.currentTimeMillis();
- }
-
- // --- wrappers ------------------------------------------------------------
-
- /** Returns the number of key-value mappings in this map.
- * @return the number of key-value mappings in this map */
- public int size ( ) { return (_val_1==TOMBSTONE?0:1) + _chm.size(); }
- /** Tests if the key in the table.
- * @return <tt>true</tt> if the key is in the table */
- public boolean containsKey( long key ) { return get(key) != null; }
-
- /** Legacy method testing if some key maps into the specified value in this
- * table. This method is identical in functionality to {@link
- * #containsValue}, and exists solely to ensure full compatibility with
- * class {@link java.util.Hashtable}, which supported this method prior to
- * introduction of the Java Collections framework.
- * @param val a value to search for
- * @return <tt>true</tt> if this map maps one or more keys to the specified value
- * @throws NullPointerException if the specified value is null */
- public boolean contains ( Object val ) { return containsValue(val); }
-
- /** Maps the specified key to the specified value in the table. The value
- * cannot be null. <p> The value can be retrieved by calling {@link #get}
- * with a key that is equal to the original key.
- * @param key key with which the specified value is to be associated
- * @param val value to be associated with the specified key
- * @return the previous value associated with <tt>key</tt>, or
- * <tt>null</tt> if there was no mapping for <tt>key</tt>
- * @throws NullPointerException if the specified value is null */
- public TypeV put ( long key, TypeV val ) { return putIfMatch( key, val,NO_MATCH_OLD);}
-
- /** Atomically, do a {@link #put} if-and-only-if the key is not mapped.
- * Useful to ensure that only a single mapping for the key exists, even if
- * many threads are trying to create the mapping in parallel.
- * @return the previous value associated with the specified key,
- * or <tt>null</tt> if there was no mapping for the key
- * @throws NullPointerException if the specified is value is null */
- public TypeV putIfAbsent( long key, TypeV val ) { return putIfMatch( key, val,TOMBSTONE );}
-
- /** Removes the key (and its corresponding value) from this map.
- * This method does nothing if the key is not in the map.
- * @return the previous value associated with <tt>key</tt>, or
- * <tt>null</tt> if there was no mapping for <tt>key</tt>*/
- public TypeV remove ( long key ) { return putIfMatch( key,TOMBSTONE,NO_MATCH_OLD);}
-
- /** Atomically do a {@link #remove(long)} if-and-only-if the key is mapped
- * to a value which is <code>equals</code> to the given value.
- * @throws NullPointerException if the specified value is null */
- public boolean remove ( long key,Object val ) { return putIfMatch( key,TOMBSTONE,val ) == val ;}
-
- /** Atomically do a <code>put(key,val)</code> if-and-only-if the key is
- * mapped to some value already.
- * @throws NullPointerException if the specified value is null */
- public TypeV replace ( long key, TypeV val ) { return putIfMatch( key, val,MATCH_ANY );}
-
- /** Atomically do a <code>put(key,newValue)</code> if-and-only-if the key is
- * mapped a value which is <code>equals</code> to <code>oldValue</code>.
- * @throws NullPointerException if the specified value is null */
- public boolean replace ( long key, TypeV oldValue, TypeV newValue ) {
- return putIfMatch( key, newValue, oldValue ) == oldValue;
- }
-
- @SuppressWarnings("unchecked")
- private TypeV putIfMatch( long key, Object newVal, Object oldVal ) {
- if (oldVal == null || newVal == null) throw new NullPointerException();
- if( key == NO_KEY ) {
- Object curVal = _val_1;
- if( oldVal == NO_MATCH_OLD || // Do we care about expected-Value at all?
- curVal == oldVal || // No instant match already?
- (oldVal == MATCH_ANY && curVal != TOMBSTONE) ||
- oldVal.equals(curVal) ) { // Expensive equals check
- if( !CAS(_val_1_offset,curVal,newVal) ) // One shot CAS update attempt
- curVal = _val_1; // Failed; get failing witness
- }
- return curVal == TOMBSTONE ? null : (TypeV)curVal; // Return the last value present
- }
- final Object res = _chm.putIfMatch( key, newVal, oldVal );
- assert !(res instanceof Prime);
- assert res != null;
- return res == TOMBSTONE ? null : (TypeV)res;
- }
-
- /** Removes all the mappings from this map. */
- public void clear() { // Smack a new empty table down
- CHM newchm = new CHM(this,new ConcurrentAutoTable(),MIN_SIZE_LOG);
- while( !CAS(_chm_offset,_chm,newchm) ) { /*Spin until the clear works*/}
- CAS(_val_1_offset,_val_1,TOMBSTONE);
- }
- // Non-atomic clear, preserving existing large arrays
- public void clear(boolean large) { // Smack a new empty table down
- _chm.clear();
- CAS(_val_1_offset,_val_1,TOMBSTONE);
- }
-
- /** Returns <tt>true</tt> if this Map maps one or more keys to the specified
- * value. <em>Note</em>: This method requires a full internal traversal of the
- * hash table and is much slower than {@link #containsKey}.
- * @param val value whose presence in this map is to be tested
- * @return <tt>true</tt> if this Map maps one or more keys to the specified value
- * @throws NullPointerException if the specified value is null */
- public boolean containsValue( Object val ) {
- if( val == null ) return false;
- if( val == _val_1 ) return true; // Key 0
- for( TypeV V : values() )
- if( V == val || V.equals(val) )
- return true;
- return false;
- }
-
- // Get *any* valid Key. Useful for making worklists out of a hashtable - the
- // hashtable can remove dup work. Repeated calls probably return the same
- // Key, so only useful if keys are removed after calling.
- public long getKey() {
- if( _val_1 != TOMBSTONE ) throw new IllegalArgumentException("Cannot return key 0 and distinguish from no key");
- return _chm._getKey();
- }
-
- // --- get -----------------------------------------------------------------
- /** Returns the value to which the specified key is mapped, or {@code null}
- * if this map contains no mapping for the key.
- * <p>More formally, if this map contains a mapping from a key {@code k} to
- * a value {@code v} such that {@code key==k}, then this method
- * returns {@code v}; otherwise it returns {@code null}. (There can be at
- * most one such mapping.)
- * @throws NullPointerException if the specified key is null */
- // Never returns a Prime nor a Tombstone.
- @SuppressWarnings("unchecked")
- public final TypeV get( long key ) {
- if( key == NO_KEY ) {
- final Object V = _val_1;
- return V == TOMBSTONE ? null : (TypeV)V;
- }
- final Object V = _chm.get_impl(key);
- assert !(V instanceof Prime); // Never return a Prime
- assert V != TOMBSTONE;
- return (TypeV)V;
- }
-
- /** Auto-boxing version of {@link #get(long)}. */
- public TypeV get ( Object key ) { return (key instanceof Long) ? get (((Long)key).longValue()) : null; }
- /** Auto-boxing version of {@link #remove(long)}. */
- public TypeV remove ( Object key ) { return (key instanceof Long) ? remove (((Long)key).longValue()) : null; }
- /** Auto-boxing version of {@link #remove(long,Object)}. */
- public boolean remove ( Object key, Object Val ) { return (key instanceof Long) && remove(((Long) key).longValue(), Val); }
- /** Auto-boxing version of {@link #containsKey(long)}. */
- public boolean containsKey( Object key ) { return (key instanceof Long) && containsKey(((Long) key).longValue()); }
- /** Auto-boxing version of {@link #putIfAbsent}. */
- public TypeV putIfAbsent( Long key, TypeV val ) { return putIfAbsent( key.longValue(), val ); }
- /** Auto-boxing version of {@link #replace}. */
- public TypeV replace( Long key, TypeV Val ) { return replace(key.longValue(), Val); }
- /** Auto-boxing version of {@link #put}. */
- public TypeV put ( Long key, TypeV val ) { return put(key.longValue(),val); }
- /** Auto-boxing version of {@link #replace}. */
- public boolean replace( Long key, TypeV oldValue, TypeV newValue ) {
- return replace(key.longValue(), oldValue, newValue);
- }
-
- // --- help_copy -----------------------------------------------------------
- // Help along an existing resize operation. This is just a fast cut-out
- // wrapper, to encourage inlining for the fast no-copy-in-progress case. We
- // always help the top-most table copy, even if there are nested table
- // copies in progress.
- private void help_copy( ) {
- // Read the top-level CHM only once. We'll try to help this copy along,
- // even if it gets promoted out from under us (i.e., the copy completes
- // and another KVS becomes the top-level copy).
- CHM topchm = _chm;
- if( topchm._newchm == null ) return; // No copy in-progress
- topchm.help_copy_impl(false);
- }
-
- // --- hash ----------------------------------------------------------------
- // Helper function to spread lousy hashCodes Throws NPE for null Key, on
- // purpose - as the first place to conveniently toss the required NPE for a
- // null Key.
- private static int hash( long h) {
- h ^= (h>>>20) ^ (h>>>12);
- h ^= (h>>> 7) ^ (h>>> 4);
- h += h<<7; // smear low bits up high, for hashcodes that only differ by 1
- return (int)h;
- }
-
-
- // --- CHM -----------------------------------------------------------------
- // The control structure for the NonBlockingHashMapLong
- private static final class CHM implements Serializable {
- // Back-pointer to top-level structure
- final NonBlockingHashMapLong _nbhml;
-
- // Size in active K,V pairs
- private ConcurrentAutoTable _size;
- public int size () { return (int)_size.get(); }
-
- // ---
- // These next 2 fields are used in the resizing heuristics, to judge when
- // it is time to resize or copy the table. Slots is a count of used-up
- // key slots, and when it nears a large fraction of the table we probably
- // end up reprobing too much. Last-resize-milli is the time since the
- // last resize; if we are running back-to-back resizes without growing
- // (because there are only a few live keys but many slots full of dead
- // keys) then we need a larger table to cut down on the churn.
-
- // Count of used slots, to tell when table is full of dead unusable slots
- private ConcurrentAutoTable _slots;
- public int slots() { return (int)_slots.get(); }
-
- // ---
- // New mappings, used during resizing.
- // The 'next' CHM - created during a resize operation. This represents
- // the new table being copied from the old one. It's the volatile
- // variable that is read as we cross from one table to the next, to get
- // the required memory orderings. It monotonically transits from null to
- // set (once).
- volatile CHM _newchm;
- private static final AtomicReferenceFieldUpdater<CHM,CHM> _newchmUpdater =
- AtomicReferenceFieldUpdater.newUpdater(CHM.class,CHM.class, "_newchm");
- // Set the _newchm field if we can. AtomicUpdaters do not fail spuriously.
- boolean CAS_newchm( CHM newchm ) {
- return _newchmUpdater.compareAndSet(this,null,newchm);
- }
- // Sometimes many threads race to create a new very large table. Only 1
- // wins the race, but the losers all allocate a junk large table with
- // hefty allocation costs. Attempt to control the overkill here by
- // throttling attempts to create a new table. I cannot really block here
- // (lest I lose the non-blocking property) but late-arriving threads can
- // give the initial resizing thread a little time to allocate the initial
- // new table. The Right Long Term Fix here is to use array-lets and
- // incrementally create the new very large array. In C I'd make the array
- // with malloc (which would mmap under the hood) which would only eat
- // virtual-address and not real memory - and after Somebody wins then we
- // could in parallel initialize the array. Java does not allow
- // un-initialized array creation (especially of ref arrays!).
- volatile long _resizers; // count of threads attempting an initial resize
- private static final AtomicLongFieldUpdater<CHM> _resizerUpdater =
- AtomicLongFieldUpdater.newUpdater(CHM.class, "_resizers");
-
- // --- key,val -------------------------------------------------------------
- // Access K,V for a given idx
- private boolean CAS_key( int idx, long old, long key ) {
- return UNSAFE.compareAndSwapLong ( _keys, rawIndex(_keys, idx), old, key );
- }
- private boolean CAS_val( int idx, Object old, Object val ) {
- return UNSAFE.compareAndSwapObject( _vals, rawIndex(_vals, idx), old, val );
- }
-
- final long [] _keys;
- final Object [] _vals;
-
- // Simple constructor
- CHM( final NonBlockingHashMapLong nbhml, ConcurrentAutoTable size, final int logsize ) {
- _nbhml = nbhml;
- _size = size;
- _slots= new ConcurrentAutoTable();
- _keys = new long [1<<logsize];
- _vals = new Object[1<<logsize];
- }
- // Non-atomic clear
- void clear() {
- _size.clear();
- _slots.clear();
- Arrays.fill(_keys,0);
- Arrays.fill(_vals,null);
- }
-
- // --- print innards
- private void print() {
- for( int i=0; i<_keys.length; i++ ) {
- long K = _keys[i];
- if( K != NO_KEY )
- print_impl(i,K,_vals[i]);
- }
- CHM newchm = _newchm; // New table, if any
- if( newchm != null ) {
- System.out.println("----");
- newchm.print();
- }
- }
-
- // --- print only the live objects
- private void print2( ) {
- for( int i=0; i<_keys.length; i++ ) {
- long K = _keys[i];
- if( K != NO_KEY ) // key is sane
- print2_impl(i,K,_vals[i]);
- }
- CHM newchm = _newchm; // New table, if any
- if( newchm != null ) {
- System.out.println("----");
- newchm.print2();
- }
- }
-
- // --- get_impl ----------------------------------------------------------
- // Never returns a Prime nor a Tombstone.
- private Object get_impl ( final long key ) {
- final int hash = hash(key);
- final int len = _keys.length;
- int idx = (hash & (len-1)); // First key hash
-
- // Main spin/reprobe loop, looking for a Key hit
- int reprobe_cnt=0;
- try {
- while( true ) {
- final long K = _keys[idx]; // Get key before volatile read, could be NO_KEY
- final Object V = _vals[idx]; // Get value before volatile read, could be null or Tombstone or Prime
- if( K == NO_KEY ) return null; // A clear miss
-
- // Key-compare
- if( key == K ) {
- // Key hit! Check for no table-copy-in-progress
- if( !(V instanceof Prime) ) { // No copy?
- if( V == TOMBSTONE) return null;
- // We need a volatile-read between reading a newly inserted Value
- // and returning the Value (so the user might end up reading the
- // stale Value contents).
- @SuppressWarnings("unused") final CHM newchm = _newchm; // VOLATILE READ before returning V
- return V;
- }
- // Key hit - but slot is (possibly partially) copied to the new table.
- // Finish the copy & retry in the new table.
- return copy_slot_and_check(idx,key).get_impl(key); // Retry in the new table
- }
- // get and put must have the same key lookup logic! But only 'put'
- // needs to force a table-resize for a too-long key-reprobe sequence.
- // Check for too-many-reprobes on get.
- if( ++reprobe_cnt >= reprobe_limit(len) ) // too many probes
- return _newchm == null // Table copy in progress?
- ? null // Nope! A clear miss
- : copy_slot_and_check(idx,key).get_impl(key); // Retry in the new table
-
- idx = (idx+((hash>>16)|1))&(len-1); // Divergent reprobe chain
- }
- } finally {
- _nbhml.record(reprobe_cnt);
- }
- }
-
- // --- putIfMatch ---------------------------------------------------------
- // Put, Remove, PutIfAbsent, etc. Return the old value. If the returned
- // value is equal to expVal (or expVal is NO_MATCH_OLD) then the put can
- // be assumed to work (although might have been immediately overwritten).
- // Only the path through copy_slot passes in an expected value of null,
- // and putIfMatch only returns a null if passed in an expected null.
- private static volatile int DUMMY_VOLATILE;
- private Object putIfMatch( final long key, final Object putval, final Object expVal ) {
- final int hash = hash(key);
- assert putval != null;
- assert !(putval instanceof Prime);
- assert !(expVal instanceof Prime);
- final int len = _keys.length;
- int idx = (hash & (len-1)); // The first key
-
- // ---
- // Key-Claim stanza: spin till we can claim a Key (or force a resizing).
- int reprobe_cnt=0;
- long K;
- Object V;
- try {
- while( true ) { // Spin till we get a Key slot
- V = _vals[idx]; // Get old value
- K = _keys[idx]; // Get current key
- if( K == NO_KEY ) { // Slot is free?
- // Found an empty Key slot - which means this Key has never been in
- // this table. No need to put a Tombstone - the Key is not here!
- if( putval == TOMBSTONE ) return TOMBSTONE; // Not-now & never-been in this table
- if( expVal == MATCH_ANY ) return TOMBSTONE; // Will not match, even after K inserts
- // Claim the zero key-slot
- if( CAS_key(idx, NO_KEY, key) ) { // Claim slot for Key
- _slots.add(1); // Raise key-slots-used count
- break; // Got it!
- }
- // CAS to claim the key-slot failed.
- //
- // This re-read of the Key points out an annoying short-coming of Java
- // CAS. Most hardware CAS's report back the existing value - so that
- // if you fail you have a *witness* - the value which caused the CAS
- // to fail. The Java API turns this into a boolean destroying the
- // witness. Re-reading does not recover the witness because another
- // thread can write over the memory after the CAS. Hence we can be in
- // the unfortunate situation of having a CAS fail *for cause* but
- // having that cause removed by a later store. This turns a
- // non-spurious-failure CAS (such as Azul has) into one that can
- // apparently spuriously fail - and we avoid apparent spurious failure
- // by not allowing Keys to ever change.
- K = _keys[idx]; // CAS failed, get updated value
- assert K != NO_KEY ; // If keys[idx] is NO_KEY, CAS shoulda worked
- }
- // Key slot was not null, there exists a Key here
- if( K == key )
- break; // Got it!
-
- // get and put must have the same key lookup logic! Lest 'get' give
- // up looking too soon.
- //topmap._reprobes.add(1);
- if( ++reprobe_cnt >= reprobe_limit(len) ) {
- // We simply must have a new table to do a 'put'. At this point a
- // 'get' will also go to the new table (if any). We do not need
- // to claim a key slot (indeed, we cannot find a free one to claim!).
- final CHM newchm = resize();
- if( expVal != null ) _nbhml.help_copy(); // help along an existing copy
- return newchm.putIfMatch(key,putval,expVal);
- }
-
- idx = (idx+((hash>>16)|1))&(len-1); // Divergent reprobe chain
- } // End of spinning till we get a Key slot
- } finally {
- _nbhml.record(reprobe_cnt);
- }
-
- while ( true ) { // Spin till we insert a value
- // ---
- // Found the proper Key slot, now update the matching Value slot. We
- // never put a null, so Value slots monotonically move from null to
- // not-null (deleted Values use Tombstone). Thus if 'V' is null we
- // fail this fast cutout and fall into the check for table-full.
- if( putval == V ) return V; // Fast cutout for no-change
-
- // See if we want to move to a new table (to avoid high average re-probe
- // counts). We only check on the initial set of a Value from null to
- // not-null (i.e., once per key-insert).
- if( (V == null && tableFull(reprobe_cnt,len)) ||
- // Or we found a Prime: resize is already in progress. The resize
- // call below will do a CAS on _newchm forcing the read.
- V instanceof Prime) {
- resize(); // Force the new table copy to start
- return copy_slot_and_check(idx,expVal).putIfMatch(key,putval,expVal);
- }
-
- // ---
- // We are finally prepared to update the existing table
- //assert !(V instanceof Prime); // always true, so IDE warnings if uncommented
-
- // Must match old, and we do not? Then bail out now. Note that either V
- // or expVal might be TOMBSTONE. Also V can be null, if we've never
- // inserted a value before. expVal can be null if we are called from
- // copy_slot.
-
- if( expVal != NO_MATCH_OLD && // Do we care about expected-Value at all?
- V != expVal && // No instant match already?
- (expVal != MATCH_ANY || V == TOMBSTONE || V == null) &&
- !(V==null && expVal == TOMBSTONE) && // Match on null/TOMBSTONE combo
- (expVal == null || !expVal.equals(V)) ) // Expensive equals check at the last
- return (V==null) ? TOMBSTONE : V; // Do not update!
-
- // Actually change the Value in the Key,Value pair
- if( CAS_val(idx, V, putval ) ) break;
-
- // CAS failed
- // Because we have no witness, we do not know why it failed.
- // Indeed, by the time we look again the value under test might have flipped
- // a thousand times and now be the expected value (despite the CAS failing).
- // Check for the never-succeed condition of a Prime value and jump to any
- // nested table, or else just re-run.
-
- // We would not need this load at all if CAS returned the value on which
- // the CAS failed (AKA witness). The new CAS semantics are supported via
- // VarHandle in JDK9.
- V = _vals[idx]; // Get new value
-
- // If a Prime'd value got installed, we need to re-run the put on the
- // new table. Otherwise we lost the CAS to another racing put.
- // Simply retry from the start.
- if( V instanceof Prime )
- return copy_slot_and_check(idx,expVal).putIfMatch(key,putval,expVal);
-
- // Simply retry from the start.
- // NOTE: need the fence, since otherwise '_vals[idx]' load could be hoisted
- // out of loop.
- int dummy = DUMMY_VOLATILE;
- }
-
- // CAS succeeded - we did the update!
- // Both normal put's and table-copy calls putIfMatch, but table-copy
- // does not (effectively) increase the number of live k/v pairs.
- if( expVal != null ) {
- // Adjust sizes - a striped counter
- if( (V == null || V == TOMBSTONE) && putval != TOMBSTONE ) _size.add( 1);
- if( !(V == null || V == TOMBSTONE) && putval == TOMBSTONE ) _size.add(-1);
- }
-
- // We won; we know the update happened as expected.
- return (V==null && expVal!=null) ? TOMBSTONE : V;
- }
-
- // --- tableFull ---------------------------------------------------------
- // Heuristic to decide if this table is too full, and we should start a
- // new table. Note that if a 'get' call has reprobed too many times and
- // decided the table must be full, then always the estimate_sum must be
- // high and we must report the table is full. If we do not, then we might
- // end up deciding that the table is not full and inserting into the
- // current table, while a 'get' has decided the same key cannot be in this
- // table because of too many reprobes. The invariant is:
- // slots.estimate_sum >= max_reprobe_cnt >= reprobe_limit(len)
- private boolean tableFull( int reprobe_cnt, int len ) {
- return
- // Do the cheap check first: we allow some number of reprobes always
- reprobe_cnt >= REPROBE_LIMIT &&
- (reprobe_cnt >= reprobe_limit(len) ||
- // More expensive check: see if the table is > 1/2 full.
- _slots.estimate_get() >= (len>>1));
- }
-
- // --- resize ------------------------------------------------------------
- // Resizing after too many probes. "How Big???" heuristics are here.
- // Callers will (not this routine) will 'help_copy' any in-progress copy.
- // Since this routine has a fast cutout for copy-already-started, callers
- // MUST 'help_copy' lest we have a path which forever runs through
- // 'resize' only to discover a copy-in-progress which never progresses.
- private CHM resize() {
- // Check for resize already in progress, probably triggered by another thread
- CHM newchm = _newchm; // VOLATILE READ
- if( newchm != null ) // See if resize is already in progress
- return newchm; // Use the new table already
-
- // No copy in-progress, so start one. First up: compute new table size.
- int oldlen = _keys.length; // Old count of K,V pairs allowed
- int sz = size(); // Get current table count of active K,V pairs
- int newsz = sz; // First size estimate
-
- // Heuristic to determine new size. We expect plenty of dead-slots-with-keys
- // and we need some decent padding to avoid endless reprobing.
- if( _nbhml._opt_for_space ) {
- // This heuristic leads to a much denser table with a higher reprobe rate
- if( sz >= (oldlen>>1) ) // If we are >50% full of keys then...
- newsz = oldlen<<1; // Double size
- } else {
- if( sz >= (oldlen>>2) ) { // If we are >25% full of keys then...
- newsz = oldlen<<1; // Double size
- if( sz >= (oldlen>>1) ) // If we are >50% full of keys then...
- newsz = oldlen<<2; // Double double size
- }
- }
-
- // Last (re)size operation was very recent? Then double again
- // despite having few live keys; slows down resize operations
- // for tables subject to a high key churn rate - but do not
- // forever grow the table. If there is a high key churn rate
- // the table needs a steady state of rare same-size resize
- // operations to clean out the dead keys.
- long tm = System.currentTimeMillis();
- if( newsz <= oldlen && // New table would shrink or hold steady?
- tm <= _nbhml._last_resize_milli+10000) // Recent resize (less than 10 sec ago)
- newsz = oldlen<<1; // Double the existing size
-
- // Do not shrink, ever. If we hit this size once, assume we
- // will again.
- if( newsz < oldlen ) newsz = oldlen;
-
- // Convert to power-of-2
- int log2;
- for( log2=MIN_SIZE_LOG; (1<<log2) < newsz; log2++ ) ; // Compute log2 of size
- long len = ((1L << log2) << 1) + 2;
- // prevent integer overflow - limit of 2^31 elements in a Java array
- // so here, 2^30 + 2 is the largest number of elements in the hash table
- if ((int)len!=len) {
- log2 = 30;
- len = (1L << log2) + 2;
- if (sz > ((len >> 2) + (len >> 1))) throw new RuntimeException("Table is full.");
- }
-
- // Now limit the number of threads actually allocating memory to a
- // handful - lest we have 750 threads all trying to allocate a giant
- // resized array.
- long r = _resizers;
- while( !_resizerUpdater.compareAndSet(this,r,r+1) )
- r = _resizers;
- // Size calculation: 2 words (K+V) per table entry, plus a handful. We
- // guess at 64-bit pointers; 32-bit pointers screws up the size calc by
- // 2x but does not screw up the heuristic very much.
- long megs = ((((1L<<log2)<<1)+8)<<3/*word to bytes*/)>>20/*megs*/;
- if( r >= 2 && megs > 0 ) { // Already 2 guys trying; wait and see
- newchm = _newchm; // Between dorking around, another thread did it
- if( newchm != null ) // See if resize is already in progress
- return newchm; // Use the new table already
- // We could use a wait with timeout, so we'll wakeup as soon as the new table
- // is ready, or after the timeout in any case.
- //synchronized( this ) { wait(8*megs); } // Timeout - we always wakeup
- // For now, sleep a tad and see if the 2 guys already trying to make
- // the table actually get around to making it happen.
- try { Thread.sleep(megs); } catch( Exception e ) { /*empty*/}
- }
- // Last check, since the 'new' below is expensive and there is a chance
- // that another thread slipped in a new thread while we ran the heuristic.
- newchm = _newchm;
- if( newchm != null ) // See if resize is already in progress
- return newchm; // Use the new table already
-
- // New CHM - actually allocate the big arrays
- newchm = new CHM(_nbhml,_size,log2);
-
- // Another check after the slow allocation
- if( _newchm != null ) // See if resize is already in progress
- return _newchm; // Use the new table already
-
- // The new table must be CAS'd in so only 1 winner amongst duplicate
- // racing resizing threads. Extra CHM's will be GC'd.
- if( CAS_newchm( newchm ) ) { // NOW a resize-is-in-progress!
- //notifyAll(); // Wake up any sleepers
- //long nano = System.nanoTime();
- //System.out.println(" "+nano+" Resize from "+oldlen+" to "+(1<<log2)+" and had "+(_resizers-1)+" extras" );
- //System.out.print("["+log2);
- } else // CAS failed?
- newchm = _newchm; // Reread new table
- return newchm;
- }
-
-
- // The next part of the table to copy. It monotonically transits from zero
- // to _keys.length. Visitors to the table can claim 'work chunks' by
- // CAS'ing this field up, then copying the indicated indices from the old
- // table to the new table. Workers are not required to finish any chunk;
- // the counter simply wraps and work is copied duplicately until somebody
- // somewhere completes the count.
- volatile long _copyIdx = 0;
- static private final AtomicLongFieldUpdater<CHM> _copyIdxUpdater =
- AtomicLongFieldUpdater.newUpdater(CHM.class, "_copyIdx");
-
- // Work-done reporting. Used to efficiently signal when we can move to
- // the new table. From 0 to len(oldkvs) refers to copying from the old
- // table to the new.
- volatile long _copyDone= 0;
- static private final AtomicLongFieldUpdater<CHM> _copyDoneUpdater =
- AtomicLongFieldUpdater.newUpdater(CHM.class, "_copyDone");
-
- // --- help_copy_impl ----------------------------------------------------
- // Help along an existing resize operation. We hope its the top-level
- // copy (it was when we started) but this CHM might have been promoted out
- // of the top position.
- private void help_copy_impl( final boolean copy_all ) {
- final CHM newchm = _newchm;
- assert newchm != null; // Already checked by caller
- int oldlen = _keys.length; // Total amount to copy
- final int MIN_COPY_WORK = Math.min(oldlen,1024); // Limit per-thread work
-
- // ---
- int panic_start = -1;
- int copyidx=-9999; // Fool javac to think it's initialized
- while( _copyDone < oldlen ) { // Still needing to copy?
- // Carve out a chunk of work. The counter wraps around so every
- // thread eventually tries to copy every slot repeatedly.
-
- // We "panic" if we have tried TWICE to copy every slot - and it still
- // has not happened. i.e., twice some thread somewhere claimed they
- // would copy 'slot X' (by bumping _copyIdx) but they never claimed to
- // have finished (by bumping _copyDone). Our choices become limited:
- // we can wait for the work-claimers to finish (and become a blocking
- // algorithm) or do the copy work ourselves. Tiny tables with huge
- // thread counts trying to copy the table often 'panic'.
- if( panic_start == -1 ) { // No panic?
- copyidx = (int)_copyIdx;
- while( copyidx < (oldlen<<1) && // 'panic' check
- !_copyIdxUpdater.compareAndSet(this,copyidx,copyidx+MIN_COPY_WORK) )
- copyidx = (int)_copyIdx; // Re-read
- if( !(copyidx < (oldlen<<1)) ) // Panic!
- panic_start = copyidx; // Record where we started to panic-copy
- }
-
- // We now know what to copy. Try to copy.
- int workdone = 0;
- for( int i=0; i<MIN_COPY_WORK; i++ )
- if( copy_slot((copyidx+i)&(oldlen-1)) ) // Made an oldtable slot go dead?
- workdone++; // Yes!
- if( workdone > 0 ) // Report work-done occasionally
- copy_check_and_promote( workdone );// See if we can promote
- //for( int i=0; i<MIN_COPY_WORK; i++ )
- // if( copy_slot((copyidx+i)&(oldlen-1)) ) // Made an oldtable slot go dead?
- // copy_check_and_promote( 1 );// See if we can promote
-
- copyidx += MIN_COPY_WORK;
- // Uncomment these next 2 lines to turn on incremental table-copy.
- // Otherwise this thread continues to copy until it is all done.
- if( !copy_all && panic_start == -1 ) // No panic?
- return; // Then done copying after doing MIN_COPY_WORK
- }
- // Extra promotion check, in case another thread finished all copying
- // then got stalled before promoting.
- copy_check_and_promote( 0 ); // See if we can promote
- }
-
-
- // --- copy_slot_and_check -----------------------------------------------
- // Copy slot 'idx' from the old table to the new table. If this thread
- // confirmed the copy, update the counters and check for promotion.
- //
- // Returns the result of reading the volatile _newchm, mostly as a
- // convenience to callers. We come here with 1-shot copy requests
- // typically because the caller has found a Prime, and has not yet read
- // the _newchm volatile - which must have changed from null-to-not-null
- // before any Prime appears. So the caller needs to read the _newchm
- // field to retry his operation in the new table, but probably has not
- // read it yet.
- private CHM copy_slot_and_check( int idx, Object should_help ) {
- // We're only here because the caller saw a Prime, which implies a
- // table-copy is in progress.
- assert _newchm != null;
- if( copy_slot(idx) ) // Copy the desired slot
- copy_check_and_promote(1); // Record the slot copied
- // Generically help along any copy (except if called recursively from a helper)
- if( should_help != null ) _nbhml.help_copy();
- return _newchm;
- }
-
- // --- copy_check_and_promote --------------------------------------------
- private void copy_check_and_promote( int workdone ) {
- int oldlen = _keys.length;
- // We made a slot unusable and so did some of the needed copy work
- long copyDone = _copyDone;
- long nowDone = copyDone+workdone;
- assert nowDone <= oldlen;
- if( workdone > 0 ) {
- while( !_copyDoneUpdater.compareAndSet(this,copyDone,nowDone) ) {
- copyDone = _copyDone; // Reload, retry
- nowDone = copyDone+workdone;
- assert nowDone <= oldlen;
- }
- }
-
- // Check for copy being ALL done, and promote. Note that we might have
- // nested in-progress copies and manage to finish a nested copy before
- // finishing the top-level copy. We only promote top-level copies.
- if( nowDone == oldlen && // Ready to promote this table?
- _nbhml._chm == this && // Looking at the top-level table?
- // Attempt to promote
- _nbhml.CAS(_chm_offset,this,_newchm) ) {
- _nbhml._last_resize_milli = System.currentTimeMillis(); // Record resize time for next check
- }
- }
-
- // --- copy_slot ---------------------------------------------------------
- // Copy one K/V pair from oldkvs[i] to newkvs. Returns true if we can
- // confirm that we set an old-table slot to TOMBPRIME, and only returns after
- // updating the new table. We need an accurate confirmed-copy count so
- // that we know when we can promote (if we promote the new table too soon,
- // other threads may 'miss' on values not-yet-copied from the old table).
- // We don't allow any direct updates on the new table, unless they first
- // happened to the old table - so that any transition in the new table from
- // null to not-null must have been from a copy_slot (or other old-table
- // overwrite) and not from a thread directly writing in the new table.
- private boolean copy_slot( int idx ) {
- // Blindly set the key slot from NO_KEY to some key which hashes here,
- // to eagerly stop fresh put's from inserting new values in the old
- // table when the old table is mid-resize. We don't need to act on the
- // results here, because our correctness stems from box'ing the Value
- // field. Slamming the Key field is a minor speed optimization.
- long key;
- while( (key=_keys[idx]) == NO_KEY )
- CAS_key(idx, NO_KEY, (idx+_keys.length)/*a non-zero key which hashes here*/);
-
- // ---
- // Prevent new values from appearing in the old table.
- // Box what we see in the old table, to prevent further updates.
- Object oldval = _vals[idx]; // Read OLD table
- while( !(oldval instanceof Prime) ) {
- final Prime box = (oldval == null || oldval == TOMBSTONE) ? TOMBPRIME : new Prime(oldval);
- if( CAS_val(idx,oldval,box) ) { // CAS down a box'd version of oldval
- // If we made the Value slot hold a TOMBPRIME, then we both
- // prevented further updates here but also the (absent) oldval is
- // vaccuously available in the new table. We return with true here:
- // any thread looking for a value for this key can correctly go
- // straight to the new table and skip looking in the old table.
- if( box == TOMBPRIME )
- return true;
- // Otherwise we boxed something, but it still needs to be
- // copied into the new table.
- oldval = box; // Record updated oldval
- break; // Break loop; oldval is now boxed by us
- }
- oldval = _vals[idx]; // Else try, try again
- }
- if( oldval == TOMBPRIME ) return false; // Copy already complete here!
-
- // ---
- // Copy the value into the new table, but only if we overwrite a null.
- // If another value is already in the new table, then somebody else
- // wrote something there and that write is happens-after any value that
- // appears in the old table.
- Object old_unboxed = ((Prime)oldval)._V;
- assert old_unboxed != TOMBSTONE;
- boolean copied_into_new = (_newchm.putIfMatch(key, old_unboxed, null) == null);
-
- // ---
- // Finally, now that any old value is exposed in the new table, we can
- // forever hide the old-table value by slapping a TOMBPRIME down. This
- // will stop other threads from uselessly attempting to copy this slot
- // (i.e., it's a speed optimization not a correctness issue).
- while( oldval != TOMBPRIME && !CAS_val(idx,oldval,TOMBPRIME) )
- oldval = _vals[idx];
-
- return copied_into_new;
- } // end copy_slot
-
- // Get *any* random key, or 0
- private long _getKey() {
- for( int i=0; i<_keys.length; i++ )
- if( _keys[i] != NO_KEY && Prime.unbox(_vals[i])!=TOMBSTONE )
- return _keys[i];
- return _newchm == null ? 0 : _newchm._getKey();
- }
-
- } // End of CHM
-
-
- // --- Snapshot ------------------------------------------------------------
- // The main class for iterating over the NBHM. It "snapshots" a clean
- // view of the K/V array.
- private class SnapshotV implements Iterator<TypeV>, Enumeration<TypeV> {
- final CHM _sschm;
- public SnapshotV() {
- CHM topchm;
- while( true ) { // Verify no table-copy-in-progress
- topchm = _chm;
- if( topchm._newchm == null ) // No table-copy-in-progress
- break;
- // Table copy in-progress - so we cannot get a clean iteration. We
- // must help finish the table copy before we can start iterating.
- topchm.help_copy_impl(true);
- }
- // The "linearization point" for the iteration. Every key in this table
- // will be visited, but keys added later might be skipped or even be
- // added to a following table (also not iterated over).
- _sschm = topchm;
- // Warm-up the iterator
- _idx = -1;
- next();
- }
- int length() { return _sschm._keys.length; }
- long key(final int idx) { return _sschm._keys[idx]; }
- private int _idx; // -2 for NO_KEY, -1 for CHECK_NEW_TABLE_LONG, 0-keys.length
- private long _nextK, _prevK; // Last 2 keys found
- private TypeV _nextV, _prevV; // Last 2 values found
- public boolean hasNext() { return _nextV != null; }
- public TypeV next() {
- // 'next' actually knows what the next value will be - it had to
- // figure that out last go 'round lest 'hasNext' report true and
- // some other thread deleted the last value. Instead, 'next'
- // spends all its effort finding the key that comes after the
- // 'next' key.
- if( _idx != -1 && _nextV == null ) throw new NoSuchElementException();
- _prevK = _nextK; // This will become the previous key
- _prevV = _nextV; // This will become the previous value
- _nextV = null; // We have no more next-key
- // Attempt to set <_nextK,_nextV> to the next K,V pair.
- // _nextV is the trigger: stop searching when it is != null
- if( _idx == -1 ) { // Check for NO_KEY
- _idx = 0; // Setup for next phase of search
- _nextK = NO_KEY;
- if( (_nextV=get(_nextK)) != null ) return _prevV;
- }
- while( _idx<length() ) { // Scan array
- _nextK = key(_idx++); // Get a key that definitely is in the set (for the moment!)
- if( _nextK != NO_KEY && // Found something?
- (_nextV=get(_nextK)) != null )
- break; // Got it! _nextK is a valid Key
- } // Else keep scanning
- return _prevV; // Return current value.
- }
-
- public void removeKey() {
- if( _prevV == null ) throw new IllegalStateException();
- NonBlockingHashMapLong.this.putIfMatch( _prevK, TOMBSTONE, NO_MATCH_OLD);
- _prevV = null;
- }
-
- @Override
- public void remove() {
- // NOTE: it would seem logical that value removal will semantically mean
- // removing the matching value for the mapping <k,v>, but the JDK always
- // removes by key, even when the value has changed.
- removeKey();
- }
-
- public TypeV nextElement() { return next(); }
- public boolean hasMoreElements() { return hasNext(); }
- }
-
- /** Returns an enumeration of the values in this table.
- * @return an enumeration of the values in this table
- * @see #values() */
- public Enumeration<TypeV> elements() { return new SnapshotV(); }
-
- // --- values --------------------------------------------------------------
- /** Returns a {@link Collection} view of the values contained in this map.
- * The collection is backed by the map, so changes to the map are reflected
- * in the collection, and vice-versa. The collection supports element
- * removal, which removes the corresponding mapping from this map, via the
- * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
- * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
- * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
- *
- * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator that
- * will never throw {@link ConcurrentModificationException}, and guarantees
- * to traverse elements as they existed upon construction of the iterator,
- * and may (but is not guaranteed to) reflect any modifications subsequent
- * to construction. */
- public Collection<TypeV> values() {
- return new AbstractCollection<TypeV>() {
- public void clear ( ) { NonBlockingHashMapLong.this.clear ( ); }
- public int size ( ) { return NonBlockingHashMapLong.this.size ( ); }
- public boolean contains( Object v ) { return NonBlockingHashMapLong.this.containsValue(v); }
- public Iterator<TypeV> iterator() { return new SnapshotV(); }
- };
- }
-
- // --- keySet --------------------------------------------------------------
- /** A class which implements the {@link Iterator} and {@link Enumeration}
- * interfaces, generified to the {@link Long} class and supporting a
- * <strong>non-auto-boxing</strong> {@link #nextLong} function. */
- public class IteratorLong implements Iterator<Long>, Enumeration<Long> {
- private final SnapshotV _ss;
- /** A new IteratorLong */
- public IteratorLong() { _ss = new SnapshotV(); }
- /** Remove last key returned by {@link #next} or {@link #nextLong}. */
- public void remove() { _ss.removeKey(); }
- /** <strong>Auto-box</strong> and return the next key. */
- public Long next () { _ss.next(); return _ss._prevK; }
- /** Return the next key as a primitive {@code long}. */
- public long nextLong() { _ss.next(); return _ss._prevK; }
- /** True if there are more keys to iterate over. */
- public boolean hasNext() { return _ss.hasNext(); }
- /** <strong>Auto-box</strong> and return the next key. */
- public Long nextElement() { return next(); }
- /** True if there are more keys to iterate over. */
- public boolean hasMoreElements() { return hasNext(); }
- }
- /** Returns an enumeration of the <strong>auto-boxed</strong> keys in this table.
- * <strong>Warning:</strong> this version will auto-box all returned keys.
- * @return an enumeration of the auto-boxed keys in this table
- * @see #keySet() */
- public Enumeration<Long> keys() { return new IteratorLong(); }
-
- /** Returns a {@link Set} view of the keys contained in this map; with care
- * the keys may be iterated over <strong>without auto-boxing</strong>. The
- * set is backed by the map, so changes to the map are reflected in the
- * set, and vice-versa. The set supports element removal, which removes
- * the corresponding mapping from this map, via the
- * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, <tt>removeAll</tt>,
- * <tt>retainAll</tt>, and <tt>clear</tt> operations. It does not support
- * the <tt>add</tt> or <tt>addAll</tt> operations.
- *
- * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator that
- * will never throw {@link ConcurrentModificationException}, and guarantees
- * to traverse elements as they existed upon construction of the iterator,
- * and may (but is not guaranteed to) reflect any modifications subsequent
- * to construction. */
- public Set<Long> keySet() {
- return new AbstractSet<Long> () {
- public void clear ( ) { NonBlockingHashMapLong.this.clear ( ); }
- public int size ( ) { return NonBlockingHashMapLong.this.size ( ); }
- public boolean contains( Object k ) { return NonBlockingHashMapLong.this.containsKey(k); }
- public boolean remove ( Object k ) { return NonBlockingHashMapLong.this.remove (k) != null; }
- public IteratorLong iterator() { return new IteratorLong(); }
- };
- }
-
- /** Keys as a long array. Array may be zero-padded if keys are concurrently deleted. */
- @SuppressWarnings("unchecked")
- public long[] keySetLong() {
- long[] dom = new long[size()];
- IteratorLong i=(IteratorLong)keySet().iterator();
- int j=0;
- while( j < dom.length && i.hasNext() )
- dom[j++] = i.nextLong();
- return dom;
- }
-
- // --- entrySet ------------------------------------------------------------
- // Warning: Each call to 'next' in this iterator constructs a new Long and a
- // new NBHMLEntry.
- private class NBHMLEntry extends AbstractEntry<Long,TypeV> {
- NBHMLEntry( final Long k, final TypeV v ) { super(k,v); }
- public TypeV setValue(final TypeV val) {
- if (val == null) throw new NullPointerException();
- _val = val;
- return put(_key, val);
- }
- }
- private class SnapshotE implements Iterator<Map.Entry<Long,TypeV>> {
- final SnapshotV _ss;
- public SnapshotE() { _ss = new SnapshotV(); }
- public void remove() {
- // NOTE: it would seem logical that entry removal will semantically mean
- // removing the matching pair <k,v>, but the JDK always removes by key,
- // even when the value has changed.
- _ss.removeKey();
- }
- public Map.Entry<Long,TypeV> next() { _ss.next(); return new NBHMLEntry(_ss._prevK,_ss._prevV); }
- public boolean hasNext() { return _ss.hasNext(); }
- }
-
- /** Returns a {@link Set} view of the mappings contained in this map. The
- * set is backed by the map, so changes to the map are reflected in the
- * set, and vice-versa. The set supports element removal, which removes
- * the corresponding mapping from the map, via the
- * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, <tt>removeAll</tt>,
- * <tt>retainAll</tt>, and <tt>clear</tt> operations. It does not support
- * the <tt>add</tt> or <tt>addAll</tt> operations.
- *
- * <p>The view's <tt>iterator</tt> is a "weakly consistent" iterator
- * that will never throw {@link ConcurrentModificationException},
- * and guarantees to traverse elements as they existed upon
- * construction of the iterator, and may (but is not guaranteed to)
- * reflect any modifications subsequent to construction.
- *
- * <p><strong>Warning:</strong> the iterator associated with this Set
- * requires the creation of {@link java.util.Map.Entry} objects with each
- * iteration. The {@link NonBlockingHashMap}
- * does not normally create or using {@link java.util.Map.Entry} objects so
- * they will be created soley to support this iteration. Iterating using
- * {@link Map#keySet} or {@link Map#values} will be more efficient. In addition,
- * this version requires <strong>auto-boxing</strong> the keys.
- */
- public Set<Map.Entry<Long,TypeV>> entrySet() {
- return new AbstractSet<Map.Entry<Long,TypeV>>() {
- public void clear ( ) { NonBlockingHashMapLong.this.clear( ); }
- public int size ( ) { return NonBlockingHashMapLong.this.size ( ); }
- public boolean remove( final Object o ) {
- if (!(o instanceof Map.Entry)) return false;
- final Map.Entry<?,?> e = (Map.Entry<?,?>)o;
- return NonBlockingHashMapLong.this.remove(e.getKey(), e.getValue());
- }
- public boolean contains(final Object o) {
- if (!(o instanceof Map.Entry)) return false;
- final Map.Entry<?,?> e = (Map.Entry<?,?>)o;
- TypeV v = get(e.getKey());
- return v != null && v.equals(e.getValue());
- }
- public Iterator<Map.Entry<Long,TypeV>> iterator() { return new SnapshotE(); }
- };
- }
-
- // --- writeObject -------------------------------------------------------
- // Write a NBHML to a stream
- private void writeObject(java.io.ObjectOutputStream s) throws IOException {
- s.defaultWriteObject(); // Write nothing
- for( long K : keySet() ) {
- final Object V = get(K); // Do an official 'get'
- s.writeLong (K); // Write the <long,TypeV> pair
- s.writeObject(V);
- }
- s.writeLong(NO_KEY); // Sentinel to indicate end-of-data
- s.writeObject(null);
- }
-
- // --- readObject --------------------------------------------------------
- // Read a CHM from a stream
- @SuppressWarnings("unchecked")
- private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
- s.defaultReadObject(); // Read nothing
- initialize(MIN_SIZE);
- for (;;) {
- final long K = s.readLong();
- final TypeV V = (TypeV) s.readObject();
- if( K == NO_KEY && V == null ) break;
- put(K,V); // Insert with an offical put
- }
- }
-
- /**
- * Creates a shallow copy of this hashtable. All the structure of the
- * hashtable itself is copied, but the keys and values are not cloned.
- * This is a relatively expensive operation.
- *
- * @return a clone of the hashtable.
- */
- @SuppressWarnings("unchecked")
- @Override
- public NonBlockingHashMapLong<TypeV> clone() {
- try {
- // Must clone, to get the class right; NBHML might have been
- // extended so it would be wrong to just make a new NBHML.
- NonBlockingHashMapLong<TypeV> t = (NonBlockingHashMapLong<TypeV>) super.clone();
- // But I don't have an atomic clone operation - the underlying _kvs
- // structure is undergoing rapid change. If I just clone the _kvs
- // field, the CHM in _kvs[0] won't be in sync.
- //
- // Wipe out the cloned array (it was shallow anyways).
- t.clear();
- // Now copy sanely
- for( long K : keySetLong() )
- t.put(K,get(K));
- return t;
- } catch (CloneNotSupportedException e) {
- // this shouldn't happen, since we are Cloneable
- throw new InternalError();
- }
- }
-
-} // End NonBlockingHashMapLong class
diff --git a/src/main/java/com/seaofnodes/util/Util.java b/src/main/java/com/seaofnodes/util/Util.java
index 416edf7e..e559a799 100644
--- a/src/main/java/com/seaofnodes/util/Util.java
+++ b/src/main/java/com/seaofnodes/util/Util.java
@@ -17,7 +17,6 @@
package com.seaofnodes.util;
-import java.util.concurrent.ConcurrentMap;
import java.util.Arrays;
public class Util {
@@ -40,61 +39,6 @@ public class Util {
return false;
}
- // Call per every get
- private static int REPROBE_CHK_CNT;
- public static void reprobe_quality_check_per(NonBlockingHashMap map, String msg) {
- if( (REPROBE_CHK_CNT++ & ((1L<<16)-1)) == 0 ) // Reports every 2^16 == 65536 gets
- reprobe_quality_check(map,msg);
- }
- public static void reprobe_quality_check(NonBlockingHashMap map, String msg) {
- System.out.print("--- Reprobe histogram "+msg+": ");
- AryInt ps = map.reprobes();
- long sum=0, ssum=0;
- for( int i=0; i<ps._len; i++ ) {
- System.out.print(""+i+":"+ps.at(i)+", ");
- long l = ps.at(i);
- sum += l;
- ssum += l*(i+1);
- }
- System.out.println();
- System.out.println("--- Reprobe rate "+msg+": ("+ssum+"/"+sum+") = "+((double)ssum/sum));
- }
- // Call per every put
- public static void hash_quality_check_per(ConcurrentMap map, String msg) {
- if( (map.size() & ((1L<<10)-1)) == 0 ) // Reports every 2^16 == 65536 puts
- hash_quality_check(map,msg);
- }
- // Call for a report
- public static void hash_quality_check(ConcurrentMap map, String msg) {
- System.out.println("--- Hash quality check for "+msg+" ---");
- NonBlockingHashMapLong<Integer> hashs = new NonBlockingHashMapLong<>();
- for( Object k : map.keySet() ) {
- int hash = k.hashCode();
- Integer ii = hashs.get(hash);
- hashs.put(hash,ii==null ? 1 : ii+1);
- }
- int[] hist = new int[32];
- int maxval=0;
- long maxkey=-1;
- for( long l : hashs.keySetLong() ) {
- int reps = hashs.get(l);
- if( reps > maxval ) { maxval=reps; maxkey=l; }
- if( reps < hist.length ) hist[reps]++;
- else System.out.println("hash "+l+" repeats "+reps);
- }
- for( int i=0; i<hist.length; i++ )
- if( hist[i] > 0 )
- System.out.println("Number of hashes with "+i+" repeats: "+hist[i]);
- System.out.println("Max repeat key "+maxkey+" repeats: "+maxval);
- int cnt=0;
- for( Object k : map.keySet() ) {
- if( k.hashCode()==maxkey ) {
- System.out.println("Sample: "+k);
- if( cnt++>3 ) break;
- }
- }
- }
-
// Copied from http://burtleburtle.net/bob/c/lookup3.c
// Call add_hash as many times as you like, then get_hash at the end.