From 4bb1baa9d4e2f4e5f1aa4695e5e7ed080333173a Mon Sep 17 00:00:00 2001 From: kimchy Date: Tue, 10 Aug 2010 00:05:38 +0300 Subject: [PATCH] remove nb structures --- .../concurrent/ConcurrentCollections.java | 21 +- .../highscalelib/AbstractEntry.java | 102 -- .../highscalelib/ConcurrentAutoTable.java | 350 ---- .../util/concurrent/highscalelib/Counter.java | 58 - .../highscalelib/NonBlockingHashMap.java | 1567 ---------------- .../highscalelib/NonBlockingHashMapLong.java | 1517 ---------------- .../highscalelib/NonBlockingHashSet.java | 134 -- .../highscalelib/NonBlockingHashtable.java | 1568 ----------------- .../NonBlockingIdentityHashMap.java | 1537 ---------------- .../highscalelib/NonBlockingSetInt.java | 555 ------ .../concurrent/highscalelib/UtilUnsafe.java | 53 - .../concurrent/highscalelib/package-info.java | 23 - 12 files changed, 9 insertions(+), 7476 deletions(-) delete mode 100644 modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/AbstractEntry.java delete mode 100644 modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/ConcurrentAutoTable.java delete mode 100644 modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/Counter.java delete mode 100644 modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingHashMap.java delete mode 100644 modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingHashMapLong.java delete mode 100644 modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingHashSet.java delete mode 100644 modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingHashtable.java delete mode 100644 modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingIdentityHashMap.java delete mode 100644 modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingSetInt.java delete mode 100644 modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/UtilUnsafe.java delete mode 100644 modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/package-info.java diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/ConcurrentCollections.java b/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/ConcurrentCollections.java index 9ff8da6c961..6a19067b313 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/ConcurrentCollections.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/ConcurrentCollections.java @@ -20,9 +20,6 @@ package org.elasticsearch.common.util.concurrent; import org.elasticsearch.common.collect.MapBackedSet; -import org.elasticsearch.common.util.concurrent.highscalelib.NonBlockingHashMap; -import org.elasticsearch.common.util.concurrent.highscalelib.NonBlockingHashMapLong; -import org.elasticsearch.common.util.concurrent.highscalelib.NonBlockingHashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -36,23 +33,23 @@ public abstract class ConcurrentCollections { private final static boolean useNonBlockingMap = Boolean.parseBoolean(System.getProperty("elasticsearch.useNonBlockingMap", "false")); public static ConcurrentMap newConcurrentMap() { - if (useNonBlockingMap) { - return new NonBlockingHashMap(); - } +// if (useNonBlockingMap) { +// return new NonBlockingHashMap(); +// } return new ConcurrentHashMap(); } public static ConcurrentMapLong newConcurrentMapLong() { - if (useNonBlockingMap) { - return new NonBlockingHashMapLong(); - } +// if (useNonBlockingMap) { +// return new NonBlockingHashMapLong(); +// } return new ConcurrentHashMapLong(); } public static Set newConcurrentSet() { - if (useNonBlockingMap) { - return new NonBlockingHashSet(); - } +// if (useNonBlockingMap) { +// return new NonBlockingHashSet(); +// } return new MapBackedSet(new ConcurrentHashMap()); } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/AbstractEntry.java b/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/AbstractEntry.java deleted file mode 100644 index 5ee85dbb102..00000000000 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/AbstractEntry.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Licensed to Elastic Search and Shay Banon under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. Elastic Search licenses this - * file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* - * Written by Cliff Click and released to the public domain, as explained at - * http://creativecommons.org/licenses/publicdomain - */ - -package org.elasticsearch.common.util.concurrent.highscalelib; - -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 the type of keys maintained by this map - * @param the type of mapped values - * @since 1.5 - */ - -abstract class AbstractEntry implements Map.Entry { - /** - * 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 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 "key.hashCode() ^ val.hashCode()" - */ - 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/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/ConcurrentAutoTable.java b/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/ConcurrentAutoTable.java deleted file mode 100644 index b5e0c8bb8f7..00000000000 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/ConcurrentAutoTable.java +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Licensed to Elastic Search and Shay Banon under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. Elastic Search licenses this - * file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* - * Written by Cliff Click and released to the public domain, as explained at - * http://creativecommons.org/licenses/publicdomain - */ - -package org.elasticsearch.common.util.concurrent.highscalelib; - -import sun.misc.Unsafe; - -import java.io.Serializable; -import java.util.concurrent.atomic.AtomicLongFieldUpdater; -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; - -/** - * 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. - *

- *

This API is overkill for simple counters (e.g. no need for the 'mask') - * and is untested as an API for making a scalable r/w lock and so is likely - * to change! - * - * @author Cliff Click - * @since 1.5 - */ - - -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_mask(x, 0); - } - - /** - * {@link #add} with -1 - */ - public void decrement() { - add_if_mask(-1L, 0); - } - - /** - * {@link #add} with +1 - */ - public void increment() { - add_if_mask(1L, 0); - } - - /** - * 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)) ; - } - - /** - * 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(0); - } - - /** - * Same as {@link #get}, included for completeness. - */ - public int intValue() { - return (int) _cat.sum(0); - } - - /** - * Same as {@link #get}, included for completeness. - */ - public long longValue() { - return _cat.sum(0); - } - - /** - * 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(0); - } - - /** - * Return the counter's {@code long} value converted to a string. - */ - public String toString() { - return _cat.toString(0); - } - - /** - * 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', if bits under - // the mask are all zero. The sum can overflow or 'x' can contain bits in - // the mask. Value is CAS'd so no counts are lost. The CAS is retried until - // it succeeds or bits are found under the mask. Returned value is the old - // value - which WILL have zero under the mask on success and WILL NOT have - // zero under the mask for failure. - - private long add_if_mask(long x, long mask) { - return _cat.add_if_mask(x, mask, hash(), this); - } - - // The underlying array of concurrently updated long counters - private volatile CAT _cat = new CAT(null, 4/*Start Small, Think Big!*/, 0L); - private static final AtomicReferenceFieldUpdater _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 final int hash() { - int h = System.identityHashCode(Thread.currentThread()); - // You would think that System.identityHashCode on the current thread - // would be a good hash fcn, but actually on SunOS 5.8 it is pretty lousy - // in the low bits. - h ^= (h >>> 20) ^ (h >>> 12); // Bit spreader, borrowed from Doug Lea - h ^= (h >>> 7) ^ (h >>> 4); - return h << 2; // 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 final 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 _resizerUpdater = - AtomicLongFieldUpdater.newUpdater(CAT.class, "_resizers"); - - private final CAT _next; - private volatile long _sum_cache; - private volatile long _fuzzy_sum_cache; - private volatile long _fuzzy_time; - private static final int MAX_SPIN = 2; - private long[] _t; // Power-of-2 array of longs - - CAT(CAT next, int sz, long init) { - _next = next; - _sum_cache = Long.MIN_VALUE; - _t = new long[sz]; - _t[0] = init; - } - - // Only add 'x' to some slot in table, hinted at by 'hash', if bits under - // the mask are all zero. The sum can overflow or 'x' can contain bits in - // the mask. Value is CAS'd so no counts are lost. The CAS is attempted - // ONCE. - - public long add_if_mask(long x, long mask, int hash, ConcurrentAutoTable master) { - long[] t = _t; - int idx = hash & (t.length - 1); - // Peel loop; try once fast - long old = t[idx]; - boolean ok = CAS(t, idx, old & ~mask, old + x); - if (_sum_cache != Long.MIN_VALUE) - _sum_cache = Long.MIN_VALUE; // Blow out cache - if (ok) return old; // Got it - if ((old & mask) != 0) return old; // Failed for bit-set under mask - // Try harder - int cnt = 0; - while (true) { - old = t[idx]; - if ((old & mask) != 0) return old; // Failed for bit-set under mask - 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; - 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? - // 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. 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. - master.CAS_cat(this, newcat); - return old; - } - - - // Return the current sum of all things in the table, stripping off mask - // before the add. Writers can be updating the table furiously, so the - // sum is only locally accurate. - - public long sum(long mask) { - long sum = _sum_cache; - if (sum != Long.MIN_VALUE) return sum; - sum = _next == null ? 0 : _next.sum(mask); // Recursively get cached sum - long[] t = _t; - for (int i = 0; i < t.length; i++) - sum += t[i] & (~mask); - _sum_cache = sum; // Cache includes recursive counts - return sum; - } - - // Fast fuzzy version. Used a cached value until it gets old, then re-up - // the cache. - - public long estimate_sum(long mask) { - // For short tables, just do the work - if (_t.length <= 64) return sum(mask); - // For bigger tables, periodically freshen a cached value - long millis = System.currentTimeMillis(); - if (_fuzzy_time != millis) { // Time marches on? - _fuzzy_sum_cache = sum(mask); // Get sum the hard way - _fuzzy_time = millis; // Indicate freshness of cached value - } - return _fuzzy_sum_cache; // Return cached sum - } - - // Update all table slots with CAS. - - public void all_or(long mask) { - long[] t = _t; - for (int i = 0; i < t.length; i++) { - boolean done = false; - while (!done) { - long old = t[i]; - done = CAS(t, i, old, old | mask); - } - } - if (_next != null) _next.all_or(mask); - if (_sum_cache != Long.MIN_VALUE) - _sum_cache = Long.MIN_VALUE; // Blow out cache - } - - public void all_and(long mask) { - long[] t = _t; - for (int i = 0; i < t.length; i++) { - boolean done = false; - while (!done) { - long old = t[i]; - done = CAS(t, i, old, old & mask); - } - } - if (_next != null) _next.all_and(mask); - if (_sum_cache != Long.MIN_VALUE) - _sum_cache = Long.MIN_VALUE; // Blow out cache - } - - // Set/stomp all table slots. No CAS. - - public void all_set(long val) { - long[] t = _t; - for (int i = 0; i < t.length; i++) - t[i] = val; - if (_next != null) _next.all_set(val); - if (_sum_cache != Long.MIN_VALUE) - _sum_cache = Long.MIN_VALUE; // Blow out cache - } - - String toString(long mask) { - return Long.toString(sum(mask)); - } - - public void print() { - long[] t = _t; - System.out.print("[sum=" + _sum_cache + "," + 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/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/Counter.java b/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/Counter.java deleted file mode 100644 index a7ac1a2c5e8..00000000000 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/Counter.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Licensed to Elastic Search and Shay Banon under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. Elastic Search licenses this - * file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* - * Written by Cliff Click and released to the public domain, as explained at - * http://creativecommons.org/licenses/publicdomain - */ - -package org.elasticsearch.common.util.concurrent.highscalelib; - -/** - * A simple high-performance counter. Merely renames the extended {@link - * org.cliffc.high_scale_lib.ConcurrentAutoTable} class to be more obvious. - * {@link org.cliffc.high_scale_lib.ConcurrentAutoTable} already has a decent - * counting API. - * - * @author Cliff Click - * @since 1.5 - */ - -public class Counter extends ConcurrentAutoTable { - - // Add the given value to current counter value. Concurrent updates will - // not be lost, but addAndGet or getAndAdd are not implemented because but - // the total counter value is not atomically updated. - //public void add( long x ); - //public void decrement(); - //public void increment(); - - // 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 all the striped counters. - //public long get(); - //public int intValue(); - //public long longValue(); - - // A cheaper 'get'. Updated only once/millisecond, but fast as a simple - // load instruction when not updating. - //public long estimate_get( ); - -} - diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingHashMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingHashMap.java deleted file mode 100644 index 21dee050edb..00000000000 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingHashMap.java +++ /dev/null @@ -1,1567 +0,0 @@ -/* - * Licensed to Elastic Search and Shay Banon under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. Elastic Search licenses this - * file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* - * Written by Cliff Click and released to the public domain, as explained at - * http://creativecommons.org/licenses/publicdomain - */ - -package org.elasticsearch.common.util.concurrent.highscalelib; - -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; - -/** - * 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 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. - *

- * This class obeys the same functional specification as {@link - * java.util.Hashtable}, and includes versions of methods corresponding to - * each method of Hashtable. However, even though all operations are - * thread-safe, operations do not entail locking and there is - * not any support for locking the entire table in a way that - * prevents all access. This class is fully interoperable with - * Hashtable in programs that rely on its thread safety but not on - * its synchronization details. - *

- *

Operations (including put) generally do not block, so may - * overlap with other update operations (including other puts and - * removes). Retrievals reflect the results of the most recently - * completed update operations holding upon their onset. For - * aggregate operations such as putAll, 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 - * not throw {@link ConcurrentModificationException}. However, - * iterators are designed to be used by only one thread at a time. - *

- *

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). - *

- *

This class and its views and iterators implement all of the - * optional methods of the {@link Map} and {@link Iterator} - * interfaces. - *

- *

Like {@link Hashtable} but unlike {@link HashMap}, this class - * does not allow null to be used as a key or value. - * - * @author Cliff Click - * @author Prashant Deva - moved hash() function out of get_impl() so it is - * not calculated multiple times. - * @version 1.1.2 - * @param the type of keys maintained by this map - * @param the type of mapped values - * @since 1.5 - */ - -public class NonBlockingHashMap - extends AbstractMap - implements ConcurrentMap, 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 long rawIndex(final Object[] ary, final int idx) { - assert idx >= 0 && idx < ary.length; - return _Obase + idx * _Oscale; - } - - // --- Setup to use Unsafe - private static final long _kvs_offset; - - static { // - Field f = null; - try { - f = NonBlockingHashMap.class.getDeclaredField("_kvs"); - } - catch (java.lang.NoSuchFieldException e) { - throw new RuntimeException(e); - } - _kvs_offset = _unsafe.objectFieldOffset(f); - } - - private final 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 final int hash(final Object key) { - int h = key.hashCode(); // The real hashCode call - // Spread bits to regularize both segment and index locations, - // using variant of single-word Wang/Jenkins hash. - h += (h << 15) ^ 0xffffcd7d; - h ^= (h >>> 10); - h += (h << 3); - h ^= (h >>> 6); - h += (h << 2) + (h << 14); - return h ^ (h >>> 16); - } - - // --- 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 final CHM chm(Object[] kvs) { - return (CHM) kvs[0]; - } - - private static final int[] hashes(Object[] kvs) { - return (int[]) kvs[1]; - } - - // Number of K,V pairs in the table - - private static final int len(Object[] kvs) { - return (kvs.length - 2) >> 1; - } - - // Time since last resize - private transient long _last_resize_milli; - - // --- Minimum table size ---------------- - // Pick size 8 K/V pairs, which turns into (8*2+2)*4+12 = 84 bytes on a - // standard 32-bit HotSpot, and (8*2+2)*8+12 = 156 bytes on 64-bit Azul. - private static final int MIN_SIZE_LOG = 3; // - 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); - - // --- 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 final Object key(Object[] kvs, int idx) { - return kvs[(idx << 1) + 2]; - } - - private static final Object val(Object[] kvs, int idx) { - return kvs[(idx << 1) + 3]; - } - - private static final boolean CAS_key(Object[] kvs, int idx, Object old, Object key) { - return _unsafe.compareAndSwapObject(kvs, rawIndex(kvs, (idx << 1) + 2), old, key); - } - - private static final 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 final 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 Counter _reprobes = new Counter(); - - /** - * 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 long reprobes() { - long r = _reprobes.get(); - _reprobes = new Counter(); - return r; - } - - - // --- 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 final int reprobe_limit(int len) { - return REPROBE_LIMIT + (len >> 2); - } - - // --- 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 final 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 << 2); 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 Counter()); // 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 size() == 0. - * - * @return size() == 0 - */ - @Override - public boolean isEmpty() { - return size() == 0; - } - - /** - * Tests if the key in the table using the equals method. - * - * @return true if the key is in the table using the equals 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 true 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. - *

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 key, or - * null if there was no mapping for key - * @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 null 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 key, or - * null if there was no mapping for key - * @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 equals 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 put(key,val) 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 put(key,newValue) if-and-only-if the key is - * mapped a value which is equals to oldValue. - * - * @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; - } - - 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 m) { - for (Map.Entry 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 - ; - } - - /** - * Returns true if this Map maps one or more keys to the specified - * value. Note: 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 true 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. - */ - @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 t = (NonBlockingHashMap) 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 entrySet view's iterator, enclosed in braces - * ("{}"). Adjacent mappings are separated by the characters - * ", " (comma and space). Each key-value mapping is rendered as - * the key followed by an equals sign ("=") 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> i = entrySet().iterator(); - if (!i.hasNext()) - return "{}"; - - StringBuilder sb = new StringBuilder(); - sb.append('{'); - for (; ;) { - Entry 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(", "); - } - } - - // --- 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. - *

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. - @Override - public TypeV get(Object key) { - final int fullhash = hash(key); // throws NullPointerException if key is null - final Object V = get_impl(this, _kvs, key, fullhash); - assert !(V instanceof Prime); // Never return a Prime - return (TypeV) V; - } - - private static final Object get_impl(final NonBlockingHashMap topmap, final Object[] kvs, final Object key, final int fullhash) { - 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; - 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 uninitalized 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, fullhash); // 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 - key == TOMBSTONE) // found a TOMBSTONE key, means no more keys in this table - return newkvs == null ? null : get_impl(topmap, topmap.help_copy(newkvs), key, fullhash); // Retry in the new table - - idx = (idx + 1) & (len - 1); // Reprobe by 1! (could now prefetch) - } - } - - // --- 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 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; - 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 - // 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. - K = key(kvs, idx); // CAS failed, get updated value - assert K != null; // If keys[idx] is null, CAS shoulda worked - } - // 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 uninitalized 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 - key == 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 + 1) & (len - 1); // Reprobe! - } // End of spinning till we get a Key slot - - // --- - // 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 - while (true) { - 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); - } - return (V == null && expVal != null) ? TOMBSTONE : V; - } - // 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); - } - } - - // --- 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 { - // Size in active K,V pairs - private final Counter _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 Counter _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 _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 _resizerUpdater = - AtomicLongFieldUpdater.newUpdater(CHM.class, "_resizers"); - - // --- - // Simple constructor - - CHM(Counter size) { - _size = size; - _slots = new Counter(); - } - - // --- 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 && - // More expensive check: see if the table is > 1/4 full. - _slots.estimate_get() >= reprobe_limit(len); - } - - // --- 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 - if (sz >= (oldlen >> 1)) // If we are >50% full of keys then... - newsz = oldlen << 2; // Double double size - } - // 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 1 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 - - // 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 32-bit pointers; 64-bit pointers screws up the size calc by - // 2x but does not screw up the heuristic very much. - int megs = ((((1 << log2) << 1) + 4) << 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(8 * 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[((1 << log2) << 1) + 2]; // 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< _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 _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 (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(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 - //for( int i=0; i 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 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 = 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; - boolean copied_into_new = (putIfMatch(topmap, newkvs, 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 (!CAS_val(oldkvs, idx, oldval, TOMBPRIME)) - oldval = val(oldkvs, idx); - - return copied_into_new; - } // 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, Enumeration { - 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); - } - - 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; - } - - 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 - // 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 && - (_nextV = get(_nextK)) != null) - break; // Got it! _nextK is a valid Key - } // 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(); - } - } - - /** - * Returns an enumeration of the values in this table. - * - * @return an enumeration of the values in this table - * @see #values() - */ - public Enumeration 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 - * Iterator.remove, Collection.remove, - * removeAll, retainAll, and clear operations. - * It does not support the add or addAll operations. - *

- *

The view's iterator 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 values() { - return new AbstractCollection() { - @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 iterator() { - return new SnapshotV(); - } - }; - } - - // --- keySet -------------------------------------------------------------- - - private class SnapshotK implements Iterator, Enumeration { - 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 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 Iterator.remove, - * Set.remove, removeAll, retainAll, and - * clear operations. It does not support the add or - * addAll operations. - *

- *

The view's iterator 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 keySet() { - return new AbstractSet() { - @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 iterator() { - return new SnapshotK(); - } - }; - } - - - // --- entrySet ------------------------------------------------------------ - // Warning: Each call to 'next' in this iterator constructs a new NBHMEntry. - - private class NBHMEntry extends AbstractEntry { - 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); - } - } - - private class SnapshotE implements Iterator> { - final SnapshotV _ss; - - public SnapshotE() { - _ss = new SnapshotV(); - } - - public void remove() { - _ss.remove(); - } - - public Map.Entry 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 - * Iterator.remove, Set.remove, removeAll, - * retainAll, and clear operations. It does not support - * the add or addAll operations. - *

- *

The view's iterator 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. - *

- *

Warning: 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> entrySet() { - return new AbstractSet>() { - @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> 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 pair - s.writeObject(V); - } - s.writeObject(null); // Sentinel to indicate end-of-data - s.writeObject(null); - } - - // --- readObject -------------------------------------------------------- - // Read a CHM from a stream - - 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/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingHashMapLong.java b/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingHashMapLong.java deleted file mode 100644 index bfab0ebb0bd..00000000000 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingHashMapLong.java +++ /dev/null @@ -1,1517 +0,0 @@ -/* - * Licensed to Elastic Search and Shay Banon under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. Elastic Search licenses this - * file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* - * Written by Cliff Click and released to the public domain, as explained at - * http://creativecommons.org/licenses/publicdomain - */ - -package org.elasticsearch.common.util.concurrent.highscalelib; - -import org.elasticsearch.common.util.concurrent.ConcurrentMapLong; -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; - -/** - * A lock-free alternate implementation of {@link java.util.ConcurrentHashMap} - * with primitive long keys, 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.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. - *

- *

The main benefit of this class over using plain {@link - * org.cliffc.high_scale_lib.NonBlockingHashMap} with {@link Long} keys is - * that it avoids the auto-boxing and unboxing costs. Since auto-boxing is - * automatic, it is easy to accidentally cause auto-boxing and negate - * the space and speed benefits. - *

- *

This class obeys the same functional specification as {@link - * java.util.Hashtable}, and includes versions of methods corresponding to - * each method of Hashtable. However, even though all operations are - * thread-safe, operations do not entail locking and there is - * not any support for locking the entire table in a way that - * prevents all access. This class is fully interoperable with - * Hashtable in programs that rely on its thread safety but not on - * its synchronization details. - *

- *

Operations (including put) generally do not block, so may - * overlap with other update operations (including other puts and - * removes). Retrievals reflect the results of the most recently - * completed update operations holding upon their onset. For - * aggregate operations such as putAll, 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 - * not throw {@link ConcurrentModificationException}. However, - * iterators are designed to be used by only one thread at a time. - *

- *

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). - *

- *

This class and its views and iterators implement all of the - * optional methods of the {@link Map} and {@link Iterator} - * interfaces. - *

- *

Like {@link Hashtable} but unlike {@link HashMap}, this class - * does not allow null to be used as a value. - * - * @author Cliff Click - * @param the type of mapped values - * @since 1.5 - */ - -public class NonBlockingHashMapLong - extends AbstractMap - implements ConcurrentMap, Serializable, ConcurrentMapLong { - - 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 Unsafe _unsafe = UtilUnsafe.getUnsafe(); - 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; - return _Obase + 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; - return _Lbase + idx * _Lscale; - } - - // --- Bits to allow Unsafe CAS'ing of the CHM field - private static final long _chm_offset; - private static final long _val_1_offset; - - static { // - Field f = null; - try { - f = NonBlockingHashMapLong.class.getDeclaredField("_chm"); - } - catch (java.lang.NoSuchFieldException e) { - throw new RuntimeException(e); - } - _chm_offset = _unsafe.objectFieldOffset(f); - - try { - f = NonBlockingHashMapLong.class.getDeclaredField("_val_1"); - } - catch (java.lang.NoSuchFieldException e) { - throw new RuntimeException(e); - } - _val_1_offset = _unsafe.objectFieldOffset(f); - } - - private final 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 = 4; // - 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 final 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 final void print2() { - System.out.println("========="); - print2_impl(-99, NO_KEY, _val_1); - _chm.print(); - System.out.println("========="); - } - - private static final void print2_impl(final int i, final long K, final Object V) { - if (V != null && Prime.unbox(V) != TOMBSTONE) - print_impl(i, K, V); - } - - // Count of reprobes - private transient Counter _reprobes = new Counter(); - - /** - * 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 long reprobes() { - long r = _reprobes.get(); - _reprobes = new Counter(); - return r; - } - - - // --- 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 final int reprobe_limit(int len) { - return REPROBE_LIMIT + (len >> 2); - } - - // --- 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 final void initialize(final int initial_sz) { - if (initial_sz < 0) throw new IllegalArgumentException(); - int i; // Convert to next largest power-of-2 - for (i = MIN_SIZE_LOG; (1 << i) < initial_sz; i++) ; - _chm = new CHM(this, new Counter(), 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) + (int) _chm.size(); - } - - /** - * Tests if the key in the table. - * - * @return true 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 true 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.

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 key, or - * null if there was no mapping for key - * @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 null 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 key, or - * null if there was no mapping for key - */ - 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 equals 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 put(key,val) 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 put(key,newValue) if-and-only-if the key is - * mapped a value which is equals to oldValue. - * - * @throws NullPointerException if the specified value is null - */ - public boolean replace(long key, TypeV oldValue, TypeV newValue) { - return putIfMatch(key, newValue, oldValue) == oldValue; - } - - private final TypeV putIfMatch(long key, Object newVal, Object oldVal) { - if (oldVal == null || newVal == null) throw new NullPointerException(); - if (key == NO_KEY) { - final 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 - CAS(_val_1_offset, curVal, newVal); // One shot CAS update attempt - 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 of the mappings from this map. - */ - public void clear() { // Smack a new empty table down - CHM newchm = new CHM(this, new Counter(), MIN_SIZE_LOG); - while (!CAS(_chm_offset, _chm, newchm)) // Spin until the clear works - ; - CAS(_val_1_offset, _val_1, TOMBSTONE); - } - - /** - * Returns true if this Map maps one or more keys to the specified - * value. Note: 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 true 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 ----------------------------------------------------------------- - - /** - * Returns the value to which the specified key is mapped, or {@code null} - * if this map contains no mapping for the key. - *

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. - 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) : false; - } - - /** - * Auto-boxing version of {@link #containsKey(long)}. - */ - public boolean containsKey(Object key) { - return (key instanceof Long) ? containsKey(((Long) key).longValue()) : false; - } - - /** - * Auto-boxing version of {@link #putIfAbsent}. - */ - public TypeV putIfAbsent(Long key, TypeV val) { - return putIfAbsent(((Long) key).longValue(), val); - } - - /** - * Auto-boxing version of {@link #replace}. - */ - public TypeV replace(Long key, TypeV Val) { - return replace(((Long) 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(((Long) 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 final 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); - } - - - // --- 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 final Counter _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 Counter _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 _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 _resizerUpdater = - AtomicLongFieldUpdater.newUpdater(CHM.class, "_resizers"); - - // --- key,val ------------------------------------------------------------- - // Access K,V for a given idx - - private final boolean CAS_key(int idx, long old, long key) { - return _unsafe.compareAndSwapLong(_keys, rawIndex(_keys, idx), old, key); - } - - private final 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, Counter size, final int logsize) { - _nbhml = nbhml; - _size = size; - _slots = new Counter(); - _keys = new long[1 << logsize]; - _vals = new Object[1 << logsize]; - } - - // --- print innards - - private final 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 final 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 final Object get_impl(final long key) { - final int len = _keys.length; - int idx = (int) (key & (len - 1)); // First key hash - - // Main spin/reprobe loop, looking for a Key hit - int reprobe_cnt = 0; - 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). - 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 + 1) & (len - 1); // Reprobe by 1! (could now prefetch) - } - } - - // --- 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 final Object putIfMatch(final long key, final Object putval, final Object expVal) { - assert putval != null; - assert !(putval instanceof Prime); - assert !(expVal instanceof Prime); - final int len = _keys.length; - int idx = (int) (key & (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 = NO_KEY; - Object V = null; - 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 putval; // Not-now & never-been in this table - // 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 + 1) & (len - 1); // Reprobe! - } // End of spinning till we get a Key slot - - // --- - // 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 - while (true) { - 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(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) _size.add(1); - if (!(V == null || V == TOMBSTONE) && putval == TOMBSTONE) _size.add(-1); - } - return (V == null && expVal != null) ? TOMBSTONE : V; - } - // Else CAS failed - 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); - } - } - - // --- 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 && - // More expensive check: see if the table is > 1/4 full. - _slots.estimate_get() >= reprobe_limit(len); - } - - // --- 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 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; 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 <= _nbhml._last_resize_milli + 10000 && // Recent resize (less than 1 sec ago) - //(q=_slots.estimate_sum()) >= (sz<<1) ) // 1/2 of keys are dead? - true) - newsz = oldlen << 1; // Double the existing size - - // Do not shrink, ever - if (newsz < oldlen) newsz = oldlen; - //System.out.println("old="+oldlen+" new="+newsz+" size()="+sz+" est_slots()="+q+" millis="+(tm-_nbhml._last_resize_milli)); - - // Convert to power-of-2 - int log2; - for (log2 = MIN_SIZE_LOG; (1 << log2) < newsz; log2++) ; // Compute log2 of size - - // 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 32-bit pointers; 64-bit pointers screws up the size calc by - // 2x but does not screw up the heuristic very much. - int megs = ((((1 << log2) << 1) + 4) << 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 - // 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(8 * 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. - 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< _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 _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(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 0) { - while (!_copyDoneUpdater.compareAndSet(this, copyDone, nowDone)) { - copyDone = _copyDone; // Reload, retry - nowDone = copyDone + workdone; - assert nowDone <= oldlen; - } - //if( (10*copyDone/oldlen) != (10*nowDone/oldlen) ) - // System.out.print(" "+nowDone*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 (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 - //long nano = System.nanoTime(); - //System.out.println(" "+nano+" Promote table "+oldlen+" to "+_newchm._keys.length); - //System.out.print("_"+oldlen+"]"); - } - } - - // --- 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(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. 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; - 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 (!CAS_val(idx, oldval, TOMBPRIME)) - oldval = _vals[idx]; - - return copied_into_new; - } // end copy_slot - } // End of CHM - - - // --- Snapshot ------------------------------------------------------------ - - private class SnapshotV implements Iterator, Enumeration { - 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 remove() { - if (_prevV == null) throw new IllegalStateException(); - _sschm.putIfMatch(_prevK, TOMBSTONE, _prevV); - _prevV = null; - } - - 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 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 - * Iterator.remove, Collection.remove, - * removeAll, retainAll, and clear operations. - * It does not support the add or addAll operations. - *

- *

The view's iterator 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 values() { - return new AbstractCollection() { - 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 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 - * non-auto-boxing {@link #nextLong} function. - */ - public class IteratorLong implements Iterator, Enumeration { - 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.remove(); - } - - /** - * Auto-box 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(); - } - - /** - * Auto-box 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 auto-boxed keys in this table. - * Warning: this version will auto-box all returned keys. - * - * @return an enumeration of the auto-boxed keys in this table - * @see #keySet() - */ - public Enumeration keys() { - return new IteratorLong(); - } - - /** - * Returns a {@link Set} view of the keys contained in this map; with care - * the keys may be iterated over without auto-boxing. 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 - * Iterator.remove, Set.remove, removeAll, - * retainAll, and clear operations. It does not support - * the add or addAll operations. - *

- *

The view's iterator 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 keySet() { - return new AbstractSet() { - 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(); - } - }; - } - - - // --- entrySet ------------------------------------------------------------ - // Warning: Each call to 'next' in this iterator constructs a new Long and a - // new NBHMLEntry. - - private class NBHMLEntry extends AbstractEntry { - 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> { - final SnapshotV _ss; - - public SnapshotE() { - _ss = new SnapshotV(); - } - - public void remove() { - _ss.remove(); - } - - public Map.Entry 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 - * Iterator.remove, Set.remove, removeAll, - * retainAll, and clear operations. It does not support - * the add or addAll operations. - *

- *

The view's iterator 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. - *

- *

Warning: the iterator associated with this Set - * requires the creation of {@link java.util.Map.Entry} objects with each - * iteration. The {@link org.cliffc.high_scale_lib.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. In addition, - * this version requires auto-boxing the keys. - */ - public Set> entrySet() { - return new AbstractSet>() { - 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.equals(e.getValue()); - } - - public Iterator> 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 pair - s.writeObject(V); - } - s.writeLong(NO_KEY); // Sentinel to indicate end-of-data - s.writeObject(null); - } - - // --- readObject -------------------------------------------------------- - // Read a CHM from a stream - - 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 - } - } - -} // End NonBlockingHashMapLong class diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingHashSet.java b/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingHashSet.java deleted file mode 100644 index 68574d84fc6..00000000000 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingHashSet.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Licensed to Elastic Search and Shay Banon under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. Elastic Search licenses this - * file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* - * Written by Cliff Click and released to the public domain, as explained at - * http://creativecommons.org/licenses/publicdomain - */ - -package org.elasticsearch.common.util.concurrent.highscalelib; - -import java.io.Serializable; -import java.util.AbstractSet; -import java.util.Iterator; -import java.util.Set; - - -/** - * A simple wrapper around {@link NonBlockingHashMap} making it implement the - * {@link Set} interface. All operations are Non-Blocking and multi-thread safe. - * - * @author Cliff Click - * @since 1.5 - */ - - -public class NonBlockingHashSet extends AbstractSet implements Serializable { - private static final Object V = ""; - - private final NonBlockingHashMap _map; - - /** - * Make a new empty {@link NonBlockingHashSet}. - */ - public NonBlockingHashSet() { - super(); - _map = new NonBlockingHashMap(); - } - - /** - * Add {@code o} to the set. - * - * @return true if {@code o} was added to the set, false - * if {@code o} was already in the set. - */ - public boolean add(final E o) { - return _map.putIfAbsent(o, V) != V; - } - - /** - * @return true if {@code o} is in the set. - */ - public boolean contains(final Object o) { - return _map.containsKey(o); - } - - /** - * Remove {@code o} from the set. - * - * @return true if {@code o} was removed to the set, false - * if {@code o} was not in the set. - */ - public boolean remove(final Object o) { - return _map.remove(o) == V; - } - - /** - * Current count of elements in the set. Due to concurrent racing updates, - * the size is only ever approximate. Updates due to the calling thread are - * immediately visible to calling thread. - * - * @return count of elements. - */ - public int size() { - return _map.size(); - } - - /** - * Empty the set. - */ - public void clear() { - _map.clear(); - } - - public Iterator iterator() { - return _map.keySet().iterator(); - } - - // --- - - /** - * Atomically make the set immutable. Future calls to mutate will throw an - * IllegalStateException. Existing mutator calls in other threads racing - * with this thread and will either throw IllegalStateException or their - * update will be visible to this thread. This implies that a simple flag - * cannot make the Set immutable, because a late-arriving update in another - * thread might see immutable flag not set yet, then mutate the Set after - * the {@link #readOnly} call returns. This call can be called concurrently - * (and indeed until the operation completes, all calls on the Set from any - * thread either complete normally or end up calling {@link #readOnly} - * internally). - *

- *

This call is useful in debugging multi-threaded programs where the - * Set is constructed in parallel, but construction completes after some - * time; and after construction the Set is only read. Making the Set - * read-only will cause updates arriving after construction is supposedly - * complete to throw an {@link IllegalStateException}. - */ - - // (1) call _map's immutable() call - // (2) get snapshot - // (3) CAS down a local map, power-of-2 larger than _map.size()+1/8th - // (4) start @ random, visit all snapshot, insert live keys - // (5) CAS _map to null, needs happens-after (4) - // (6) if Set call sees _map is null, needs happens-after (4) for readers - public void readOnly() { - throw new RuntimeException("Unimplemented"); - } -} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingHashtable.java b/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingHashtable.java deleted file mode 100644 index 03c7298e2b6..00000000000 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingHashtable.java +++ /dev/null @@ -1,1568 +0,0 @@ -/* - * Licensed to Elastic Search and Shay Banon under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. Elastic Search licenses this - * file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* - * Written by Cliff Click and released to the public domain, as explained at - * http://creativecommons.org/licenses/publicdomain - */ - -/* WARNING: MACHINE GENERATED FILE! DO NOT EDIT!*/ -package org.elasticsearch.common.util.concurrent.highscalelib; - -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; - -/** - * 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 NonBlockingHashtable} 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. - *

- * This class obeys the same functional specification as {@link - * java.util.Hashtable}, and includes versions of methods corresponding to - * each method of Hashtable. However, even though all operations are - * thread-safe, operations do not entail locking and there is - * not any support for locking the entire table in a way that - * prevents all access. This class is fully interoperable with - * Hashtable in programs that rely on its thread safety but not on - * its synchronization details. - *

- *

Operations (including put) generally do not block, so may - * overlap with other update operations (including other puts and - * removes). Retrievals reflect the results of the most recently - * completed update operations holding upon their onset. For - * aggregate operations such as putAll, 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 - * not throw {@link ConcurrentModificationException}. However, - * iterators are designed to be used by only one thread at a time. - *

- *

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). - *

- *

This class and its views and iterators implement all of the - * optional methods of the {@link Map} and {@link Iterator} - * interfaces. - *

- *

Like {@link Hashtable} but unlike {@link HashMap}, this class - * does not allow null to be used as a key or value. - * - * @author Cliff Click - * @author Prashant Deva - moved hash() function out of get_impl() so it is - * not calculated multiple times. - * @version 1.1.2 - * @param the type of keys maintained by this map - * @param the type of mapped values - * @since 1.5 - */ - -public class NonBlockingHashtable - extends Dictionary - implements ConcurrentMap, 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 long rawIndex(final Object[] ary, final int idx) { - assert idx >= 0 && idx < ary.length; - return _Obase + idx * _Oscale; - } - - // --- Setup to use Unsafe - private static final long _kvs_offset; - - static { // - Field f = null; - try { - f = NonBlockingHashtable.class.getDeclaredField("_kvs"); - } - catch (java.lang.NoSuchFieldException e) { - throw new RuntimeException(e); - } - _kvs_offset = _unsafe.objectFieldOffset(f); - } - - private final 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 final int hash(final Object key) { - int h = key.hashCode(); // The real hashCode call - // Spread bits to regularize both segment and index locations, - // using variant of single-word Wang/Jenkins hash. - h += (h << 15) ^ 0xffffcd7d; - h ^= (h >>> 10); - h += (h << 3); - h ^= (h >>> 6); - h += (h << 2) + (h << 14); - return h ^ (h >>> 16); - } - - // --- 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 final CHM chm(Object[] kvs) { - return (CHM) kvs[0]; - } - - private static final int[] hashes(Object[] kvs) { - return (int[]) kvs[1]; - } - - // Number of K,V pairs in the table - - private static final int len(Object[] kvs) { - return (kvs.length - 2) >> 1; - } - - // Time since last resize - private transient long _last_resize_milli; - - // --- Minimum table size ---------------- - // Pick size 8 K/V pairs, which turns into (8*2+2)*4+12 = 84 bytes on a - // standard 32-bit HotSpot, and (8*2+2)*8+12 = 156 bytes on 64-bit Azul. - private static final int MIN_SIZE_LOG = 3; // - 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); - - // --- 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 final Object key(Object[] kvs, int idx) { - return kvs[(idx << 1) + 2]; - } - - private static final Object val(Object[] kvs, int idx) { - return kvs[(idx << 1) + 3]; - } - - private static final boolean CAS_key(Object[] kvs, int idx, Object old, Object key) { - return _unsafe.compareAndSwapObject(kvs, rawIndex(kvs, (idx << 1) + 2), old, key); - } - - private static final 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 final 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 Counter _reprobes = new Counter(); - - /** - * 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 long reprobes() { - long r = _reprobes.get(); - _reprobes = new Counter(); - return r; - } - - - // --- 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 final int reprobe_limit(int len) { - return REPROBE_LIMIT + (len >> 2); - } - - // --- NonBlockingHashtable -------------------------------------------------- - // Constructors - - /** - * Create a new NonBlockingHashtable with default minimum size (currently set - * to 8 K/V pairs or roughly 84 bytes on a standard 32-bit JVM). - */ - public NonBlockingHashtable() { - this(MIN_SIZE); - } - - /** - * Create a new NonBlockingHashtable 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 NonBlockingHashtable(final int initial_sz) { - initialize(initial_sz); - } - - private final 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 << 2); 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 Counter()); // 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 size() == 0. - * - * @return size() == 0 - */ - @Override - public boolean isEmpty() { - return size() == 0; - } - - /** - * Tests if the key in the table using the equals method. - * - * @return true if the key is in the table using the equals 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 true 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. - *

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 key, or - * null if there was no mapping for key - * @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 null 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 key, or - * null if there was no mapping for key - * @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 equals 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 put(key,val) 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 put(key,newValue) if-and-only-if the key is - * mapped a value which is equals to oldValue. - * - * @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; - } - - 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 m) { - for (Map.Entry 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 NonBlockingHashtable(MIN_SIZE)._kvs; - while (!CAS_kvs(_kvs, newkvs)) // Spin until the clear works - ; - } - - /** - * Returns true if this Map maps one or more keys to the specified - * value. Note: 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 true 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. - */ - @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. - NonBlockingHashtable t = (NonBlockingHashtable) 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 entrySet view's iterator, enclosed in braces - * ("{}"). Adjacent mappings are separated by the characters - * ", " (comma and space). Each key-value mapping is rendered as - * the key followed by an equals sign ("=") 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> i = entrySet().iterator(); - if (!i.hasNext()) - return "{}"; - - StringBuilder sb = new StringBuilder(); - sb.append('{'); - for (; ;) { - Entry 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(", "); - } - } - - // --- 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. - *

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. - @Override - public TypeV get(Object key) { - final int fullhash = hash(key); // throws NullPointerException if key is null - final Object V = get_impl(this, _kvs, key, fullhash); - assert !(V instanceof Prime); // Never return a Prime - return (TypeV) V; - } - - private static final Object get_impl(final NonBlockingHashtable topmap, final Object[] kvs, final Object key, final int fullhash) { - 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; - 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 uninitalized 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, fullhash); // 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 - key == TOMBSTONE) // found a TOMBSTONE key, means no more keys in this table - return newkvs == null ? null : get_impl(topmap, topmap.help_copy(newkvs), key, fullhash); // Retry in the new table - - idx = (idx + 1) & (len - 1); // Reprobe by 1! (could now prefetch) - } - } - - // --- 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 final Object putIfMatch(final NonBlockingHashtable 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; - 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 - // 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. - K = key(kvs, idx); // CAS failed, get updated value - assert K != null; // If keys[idx] is null, CAS shoulda worked - } - // 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 uninitalized 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 - key == 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 + 1) & (len - 1); // Reprobe! - } // End of spinning till we get a Key slot - - // --- - // 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 - while (true) { - 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); - } - return (V == null && expVal != null) ? TOMBSTONE : V; - } - // 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); - } - } - - // --- 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 NonBlockingHashtable - - private static final class CHM { - // Size in active K,V pairs - private final Counter _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 Counter _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 _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 _resizerUpdater = - AtomicLongFieldUpdater.newUpdater(CHM.class, "_resizers"); - - // --- - // Simple constructor - - CHM(Counter size) { - _size = size; - _slots = new Counter(); - } - - // --- 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 && - // More expensive check: see if the table is > 1/4 full. - _slots.estimate_get() >= reprobe_limit(len); - } - - // --- 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(NonBlockingHashtable 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 - if (sz >= (oldlen >> 1)) // If we are >50% full of keys then... - newsz = oldlen << 2; // Double double size - } - // 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 1 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 - - // 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 32-bit pointers; 64-bit pointers screws up the size calc by - // 2x but does not screw up the heuristic very much. - int megs = ((((1 << log2) << 1) + 4) << 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(8 * 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[((1 << log2) << 1) + 2]; // 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< _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 _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(NonBlockingHashtable 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 (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(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 - //for( int i=0; i 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(NonBlockingHashtable 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 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 = 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; - boolean copied_into_new = (putIfMatch(topmap, newkvs, 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 (!CAS_val(oldkvs, idx, oldval, TOMBPRIME)) - oldval = val(oldkvs, idx); - - return copied_into_new; - } // 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, Enumeration { - 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(NonBlockingHashtable.this, topkvs, true); - } - // Warm-up the iterator - next(); - } - - int length() { - return len(_sskvs); - } - - Object key(int idx) { - return NonBlockingHashtable.key(_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; - } - - 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 - // 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 && - (_nextV = get(_nextK)) != null) - break; // Got it! _nextK is a valid Key - } // Else keep scanning - return _prevV; // Return current value. - } - - public void remove() { - if (_prevV == null) throw new IllegalStateException(); - putIfMatch(NonBlockingHashtable.this, _sskvs, _prevK, TOMBSTONE, _prevV); - _prevV = null; - } - - 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 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 - * Iterator.remove, Collection.remove, - * removeAll, retainAll, and clear operations. - * It does not support the add or addAll operations. - *

- *

The view's iterator 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 values() { - return new AbstractCollection() { - @Override public void clear() { - NonBlockingHashtable.this.clear(); - } - - @Override public int size() { - return NonBlockingHashtable.this.size(); - } - - @Override public boolean contains(Object v) { - return NonBlockingHashtable.this.containsValue(v); - } - - @Override public Iterator iterator() { - return new SnapshotV(); - } - }; - } - - // --- keySet -------------------------------------------------------------- - - private class SnapshotK implements Iterator, Enumeration { - 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 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 Iterator.remove, - * Set.remove, removeAll, retainAll, and - * clear operations. It does not support the add or - * addAll operations. - *

- *

The view's iterator 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 keySet() { - return new AbstractSet() { - @Override public void clear() { - NonBlockingHashtable.this.clear(); - } - - @Override public int size() { - return NonBlockingHashtable.this.size(); - } - - @Override public boolean contains(Object k) { - return NonBlockingHashtable.this.containsKey(k); - } - - @Override public boolean remove(Object k) { - return NonBlockingHashtable.this.remove(k) != null; - } - - @Override public Iterator iterator() { - return new SnapshotK(); - } - }; - } - - - // --- entrySet ------------------------------------------------------------ - // Warning: Each call to 'next' in this iterator constructs a new NBHMEntry. - - private class NBHMEntry extends AbstractEntry { - 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); - } - } - - private class SnapshotE implements Iterator> { - final SnapshotV _ss; - - public SnapshotE() { - _ss = new SnapshotV(); - } - - public void remove() { - _ss.remove(); - } - - public Map.Entry 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 - * Iterator.remove, Set.remove, removeAll, - * retainAll, and clear operations. It does not support - * the add or addAll operations. - *

- *

The view's iterator 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. - *

- *

Warning: the iterator associated with this Set - * requires the creation of {@link java.util.Map.Entry} objects with each - * iteration. The {@link NonBlockingHashtable} 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> entrySet() { - return new AbstractSet>() { - @Override public void clear() { - NonBlockingHashtable.this.clear(); - } - - @Override public int size() { - return NonBlockingHashtable.this.size(); - } - - @Override public boolean remove(final Object o) { - if (!(o instanceof Map.Entry)) return false; - final Map.Entry e = (Map.Entry) o; - return NonBlockingHashtable.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> 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 pair - s.writeObject(V); - } - s.writeObject(null); // Sentinel to indicate end-of-data - s.writeObject(null); - } - - // --- readObject -------------------------------------------------------- - // Read a CHM from a stream - - 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 NonBlockingHashtable class diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingIdentityHashMap.java b/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingIdentityHashMap.java deleted file mode 100644 index ff5fcffcdc6..00000000000 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingIdentityHashMap.java +++ /dev/null @@ -1,1537 +0,0 @@ -/* - * Licensed to Elastic Search and Shay Banon under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. Elastic Search licenses this - * file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* - * Written by Cliff Click and released to the public domain, as explained at - * http://creativecommons.org/licenses/publicdomain - */ - -package org.elasticsearch.common.util.concurrent.highscalelib; - -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; - -/** - * 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 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. - *

- * This class obeys the same functional specification as {@link - * java.util.Hashtable}, and includes versions of methods corresponding to - * each method of Hashtable. However, even though all operations are - * thread-safe, operations do not entail locking and there is - * not any support for locking the entire table in a way that - * prevents all access. This class is fully interoperable with - * Hashtable in programs that rely on its thread safety but not on - * its synchronization details. - *

- *

Operations (including put) generally do not block, so may - * overlap with other update operations (including other puts and - * removes). Retrievals reflect the results of the most recently - * completed update operations holding upon their onset. For - * aggregate operations such as putAll, 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 - * not throw {@link ConcurrentModificationException}. However, - * iterators are designed to be used by only one thread at a time. - *

- *

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). - *

- *

This class and its views and iterators implement all of the - * optional methods of the {@link Map} and {@link Iterator} - * interfaces. - *

- *

Like {@link Hashtable} but unlike {@link HashMap}, this class - * does not allow null to be used as a key or value. - * - * @author Cliff Click - * @author Prashant Deva - * Modified from original NonBlockingHashMap to use identity equality. - * Uses System.identityHashCode() to calculate hashMap. - * Key equality is compared using '=='. - * @param the type of keys maintained by this map - * @param the type of mapped values - * @since 1.5 - */ - -public class NonBlockingIdentityHashMap - extends AbstractMap - implements ConcurrentMap, 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 long rawIndex(final Object[] ary, final int idx) { - assert idx >= 0 && idx < ary.length; - return _Obase + idx * _Oscale; - } - - // --- Setup to use Unsafe - private static final long _kvs_offset; - - static { // - Field f = null; - try { - f = NonBlockingHashMap.class.getDeclaredField("_kvs"); - } - catch (java.lang.NoSuchFieldException e) { - throw new RuntimeException(e); - } - _kvs_offset = _unsafe.objectFieldOffset(f); - } - - private final 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 final int hash(final Object key) { - int h = System.identityHashCode(key); // 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 final CHM chm(Object[] kvs) { - return (CHM) kvs[0]; - } - - private static final int[] hashes(Object[] kvs) { - return (int[]) kvs[1]; - } - - // Number of K,V pairs in the table - - private static final int len(Object[] kvs) { - return (kvs.length - 2) >> 1; - } - - // Time since last resize - private transient long _last_resize_milli; - - // --- Minimum table size ---------------- - // Pick size 8 K/V pairs, which turns into (8*2+2)*4+12 = 84 bytes on a - // standard 32-bit HotSpot, and (8*2+2)*8+12 = 156 bytes on 64-bit Azul. - private static final int MIN_SIZE_LOG = 3; // - 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); - - // --- 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 final Object key(Object[] kvs, int idx) { - return kvs[(idx << 1) + 2]; - } - - private static final Object val(Object[] kvs, int idx) { - return kvs[(idx << 1) + 3]; - } - - private static final boolean CAS_key(Object[] kvs, int idx, Object old, Object key) { - return _unsafe.compareAndSwapObject(kvs, rawIndex(kvs, (idx << 1) + 2), old, key); - } - - private static final 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 final 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 Counter _reprobes = new Counter(); - - /** - * 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 long reprobes() { - long r = _reprobes.get(); - _reprobes = new Counter(); - return r; - } - - - // --- 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 final int reprobe_limit(int len) { - return REPROBE_LIMIT + (len >> 2); - } - - // --- 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 NonBlockingIdentityHashMap() { - 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 NonBlockingIdentityHashMap(final int initial_sz) { - initialize(initial_sz); - } - - private final 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 << 2); 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 Counter()); // 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 size() == 0. - * - * @return size() == 0 - */ - @Override - public boolean isEmpty() { - return size() == 0; - } - - /** - * Tests if the key in the table using the equals method. - * - * @return true if the key is in the table using the equals 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 true 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. - *

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 key, or - * null if there was no mapping for key - * @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 null 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 key, or - * null if there was no mapping for key - * @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 equals 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 put(key,val) 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 put(key,newValue) if-and-only-if the key is - * mapped a value which is equals to oldValue. - * - * @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; - } - - 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 m) { - for (Map.Entry 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 NonBlockingIdentityHashMap(MIN_SIZE)._kvs; - while (!CAS_kvs(_kvs, newkvs)) // Spin until the clear works - ; - } - - /** - * Returns true if this Map maps one or more keys to the specified - * value. Note: 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 true 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. - */ - @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. - NonBlockingIdentityHashMap t = (NonBlockingIdentityHashMap) 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 entrySet view's iterator, enclosed in braces - * ("{}"). Adjacent mappings are separated by the characters - * ", " (comma and space). Each key-value mapping is rendered as - * the key followed by an equals sign ("=") 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> i = entrySet().iterator(); - if (!i.hasNext()) - return "{}"; - - StringBuilder sb = new StringBuilder(); - sb.append('{'); - for (; ;) { - Entry 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 ----------------------------------------------------------------- - - /** - * Returns the value to which the specified key is mapped, or {@code null} - * if this map contains no mapping for the key. - *

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. - @Override - public TypeV get(Object key) { - final int fullhash = hash(key); // throws NullPointerException if key is null - final Object V = get_impl(this, _kvs, key, fullhash); - assert !(V instanceof Prime); // Never return a Prime - return (TypeV) V; - } - - private static final Object get_impl(final NonBlockingIdentityHashMap topmap, final Object[] kvs, final Object key, final int fullhash) { - 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 - - int idx = fullhash & (len - 1); // First key hash - - // Main spin/reprobe loop, looking for a Key hit - int reprobe_cnt = 0; - 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 uninitalized 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 (K == key) { - // 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, fullhash); // 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 - key == TOMBSTONE) // found a TOMBSTONE key, means no more keys in this table - return newkvs == null ? null : get_impl(topmap, topmap.help_copy(newkvs), key, fullhash); // Retry in the new table - - idx = (idx + 1) & (len - 1); // Reprobe by 1! (could now prefetch) - } - } - - // --- 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 final Object putIfMatch(final NonBlockingIdentityHashMap 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] - 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; - 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 - // 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 - 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 = key(kvs, idx); // CAS failed, get updated value - assert K != null; // If keys[idx] is null, CAS shoulda worked - } - // 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 uninitalized Key body. - // Annoyingly this means we have to volatile-read before EACH key compare. - newkvs = chm._newkvs; // VOLATILE READ before key compare - - 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) || // too many probes or - key == 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 + 1) & (len - 1); // Reprobe! - } // End of spinning till we get a Key slot - - // --- - // 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 - while (true) { - 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); - } - return (V == null && expVal != null) ? TOMBSTONE : V; - } - // 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); - } - } - - // --- 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 NonBlockingIdentityHashMap - - private static final class CHM { - // Size in active K,V pairs - private final Counter _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 Counter _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 _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 _resizerUpdater = - AtomicLongFieldUpdater.newUpdater(CHM.class, "_resizers"); - - // --- - // Simple constructor - - CHM(Counter size) { - _size = size; - _slots = new Counter(); - } - - // --- 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 && - // More expensive check: see if the table is > 1/4 full. - _slots.estimate_get() >= reprobe_limit(len); - } - - // --- 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(NonBlockingIdentityHashMap 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 - if (sz >= (oldlen >> 1)) // If we are >50% full of keys then... - newsz = oldlen << 2; // Double double size - } - // 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 1 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 - - // 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 32-bit pointers; 64-bit pointers screws up the size calc by - // 2x but does not screw up the heuristic very much. - int megs = ((((1 << log2) << 1) + 4) << 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(8 * 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[((1 << log2) << 1) + 2]; // 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< _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 _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(NonBlockingIdentityHashMap 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 (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(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 - //for( int i=0; i 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(NonBlockingIdentityHashMap 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 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 = 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; - boolean copied_into_new = (putIfMatch(topmap, newkvs, 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 (!CAS_val(oldkvs, idx, oldval, TOMBPRIME)) - oldval = val(oldkvs, idx); - - return copied_into_new; - } // 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, Enumeration { - 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(NonBlockingIdentityHashMap.this, topkvs, true); - } - // Warm-up the iterator - next(); - } - - int length() { - return len(_sskvs); - } - - Object key(int idx) { - return NonBlockingIdentityHashMap.key(_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; - } - - 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 - // 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 && - (_nextV = get(_nextK)) != null) - break; // Got it! _nextK is a valid Key - } // Else keep scanning - return _prevV; // Return current value. - } - - public void remove() { - if (_prevV == null) throw new IllegalStateException(); - putIfMatch(NonBlockingIdentityHashMap.this, _sskvs, _prevK, TOMBSTONE, _prevV); - _prevV = null; - } - - 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 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 - * Iterator.remove, Collection.remove, - * removeAll, retainAll, and clear operations. - * It does not support the add or addAll operations. - *

- *

The view's iterator 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 values() { - return new AbstractCollection() { - @Override public void clear() { - NonBlockingIdentityHashMap.this.clear(); - } - - @Override public int size() { - return NonBlockingIdentityHashMap.this.size(); - } - - @Override public boolean contains(Object v) { - return NonBlockingIdentityHashMap.this.containsValue(v); - } - - @Override public Iterator iterator() { - return new SnapshotV(); - } - }; - } - - // --- keySet -------------------------------------------------------------- - - private class SnapshotK implements Iterator, Enumeration { - 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 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 Iterator.remove, - * Set.remove, removeAll, retainAll, and - * clear operations. It does not support the add or - * addAll operations. - *

- *

The view's iterator 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 keySet() { - return new AbstractSet() { - @Override public void clear() { - NonBlockingIdentityHashMap.this.clear(); - } - - @Override public int size() { - return NonBlockingIdentityHashMap.this.size(); - } - - @Override public boolean contains(Object k) { - return NonBlockingIdentityHashMap.this.containsKey(k); - } - - @Override public boolean remove(Object k) { - return NonBlockingIdentityHashMap.this.remove(k) != null; - } - - @Override public Iterator iterator() { - return new SnapshotK(); - } - }; - } - - - // --- entrySet ------------------------------------------------------------ - // Warning: Each call to 'next' in this iterator constructs a new NBHMEntry. - - private class NBHMEntry extends AbstractEntry { - 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); - } - } - - private class SnapshotE implements Iterator> { - final SnapshotV _ss; - - public SnapshotE() { - _ss = new SnapshotV(); - } - - public void remove() { - _ss.remove(); - } - - public Map.Entry 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 - * Iterator.remove, Set.remove, removeAll, - * retainAll, and clear operations. It does not support - * the add or addAll operations. - *

- *

The view's iterator 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. - *

- *

Warning: the iterator associated with this Set - * requires the creation of {@link java.util.Map.Entry} objects with each - * iteration. The {@link NonBlockingIdentityHashMap} 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> entrySet() { - return new AbstractSet>() { - @Override public void clear() { - NonBlockingIdentityHashMap.this.clear(); - } - - @Override public int size() { - return NonBlockingIdentityHashMap.this.size(); - } - - @Override public boolean remove(final Object o) { - if (!(o instanceof Map.Entry)) return false; - final Map.Entry e = (Map.Entry) o; - return NonBlockingIdentityHashMap.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> 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 pair - s.writeObject(V); - } - s.writeObject(null); // Sentinel to indicate end-of-data - s.writeObject(null); - } - - // --- readObject -------------------------------------------------------- - // Read a CHM from a stream - - 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 NonBlockingIdentityHashMap class diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingSetInt.java b/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingSetInt.java deleted file mode 100644 index 8ccbf879c94..00000000000 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/NonBlockingSetInt.java +++ /dev/null @@ -1,555 +0,0 @@ -/* - * Licensed to Elastic Search and Shay Banon under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. Elastic Search licenses this - * file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* - * Written by Cliff Click and released to the public domain, as explained at - * http://creativecommons.org/licenses/publicdomain - */ - -package org.elasticsearch.common.util.concurrent.highscalelib; - -import sun.misc.Unsafe; - -import java.io.IOException; -import java.io.Serializable; -import java.lang.reflect.Field; -import java.util.AbstractSet; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * A multi-threaded bit-vector set, implemented as an array of primitive - * {@code longs}. All operations are non-blocking and multi-threaded safe. - * {@link #contains(int)} calls are roughly the same speed as a {load, mask} - * sequence. {@link #add(int)} and {@link #remove(int)} calls are a tad more - * expensive than a {load, mask, store} sequence because they must use a CAS. - * The bit-vector is auto-sizing. - *

- *

General note of caution: The Set API allows the use of {@link Integer} - * with silent autoboxing - which can be very expensive if many calls are - * being made. Since autoboxing is silent you may not be aware that this is - * going on. The built-in API takes lower-case {@code ints} and is much more - * efficient. - *

- *

Space: space is used in proportion to the largest element, as opposed to - * the number of elements (as is the case with hash-table based Set - * implementations). Space is approximately (largest_element/8 + 64) bytes. - *

- * The implementation is a simple bit-vector using CAS for update. - * - * @author Cliff Click - * @since 1.5 - */ - -public class NonBlockingSetInt extends AbstractSet implements Serializable { - private static final long serialVersionUID = 1234123412341234123L; - private static final Unsafe _unsafe = UtilUnsafe.getUnsafe(); - - // --- Bits to allow atomic update of the NBSI - private static final long _nbsi_offset; - - static { // - Field f = null; - try { - f = NonBlockingSetInt.class.getDeclaredField("_nbsi"); - } catch (java.lang.NoSuchFieldException e) { - } - _nbsi_offset = _unsafe.objectFieldOffset(f); - } - - private final boolean CAS_nbsi(NBSI old, NBSI nnn) { - return _unsafe.compareAndSwapObject(this, _nbsi_offset, old, nnn); - } - - // The actual Set of Joy, which changes during a resize event. The - // Only Field for this class, so I can atomically change the entire - // set implementation with a single CAS. - private transient NBSI _nbsi; - - /** - * Create a new empty bit-vector - */ - public NonBlockingSetInt() { - _nbsi = new NBSI(63, new Counter(), this); // The initial 1-word set - } - - /** - * Add {@code i} to the set. Uppercase {@link Integer} version of add, - * requires auto-unboxing. When possible use the {@code int} version of - * {@link #add(int)} for efficiency. - * - * @return true if i was added to the set. - * @throws IllegalArgumentException if i is negative. - */ - public boolean add(final Integer i) { - return add(i.intValue()); - } - - /** - * Test if {@code o} is in the set. This is the uppercase {@link Integer} - * version of contains, requires a type-check and auto-unboxing. When - * possible use the {@code int} version of {@link #contains(int)} for - * efficiency. - * - * @return true if i was in the set. - */ - public boolean contains(final Object o) { - return o instanceof Integer ? contains(((Integer) o).intValue()) : false; - } - - /** - * Remove {@code o} from the set. This is the uppercase {@link Integer} - * version of remove, requires a type-check and auto-unboxing. When - * possible use the {@code int} version of {@link #remove(int)} for - * efficiency. - * - * @return true if i was removed to the set. - */ - public boolean remove(final Object o) { - return o instanceof Integer ? remove(((Integer) o).intValue()) : false; - } - - /** - * Add {@code i} to the set. This is the lower-case '{@code int}' version - * of {@link #add} - no autoboxing. Negative values throw - * IllegalArgumentException. - * - * @return true if i was added to the set. - * @throws IllegalArgumentException if i is negative. - */ - public boolean add(final int i) { - if (i < 0) throw new IllegalArgumentException("" + i); - return _nbsi.add(i); - } - - /** - * Test if {@code i} is in the set. This is the lower-case '{@code int}' - * version of {@link #contains} - no autoboxing. - * - * @return true if i was int the set. - */ - public boolean contains(final int i) { - return i < 0 ? false : _nbsi.contains(i); - } - - /** - * Remove {@code i} from the set. This is the fast lower-case '{@code int}' - * version of {@link #remove} - no autoboxing. - * - * @return true if i was added to the set. - */ - public boolean remove(final int i) { - return i < 0 ? false : _nbsi.remove(i); - } - - /** - * Current count of elements in the set. Due to concurrent racing updates, - * the size is only ever approximate. Updates due to the calling thread are - * immediately visible to calling thread. - * - * @return count of elements. - */ - public int size() { - return _nbsi.size(); - } - - /** - * Empty the bitvector. - */ - public void clear() { - NBSI cleared = new NBSI(63, new Counter(), this); // An empty initial NBSI - while (!CAS_nbsi(_nbsi, cleared)) // Spin until clear works - ; - } - - /** - * Verbose printout of internal structure for debugging. - */ - public void print() { - _nbsi.print(0); - } - - /** - * Standard Java {@link Iterator}. Not very efficient because it - * auto-boxes the returned values. - */ - public Iterator iterator() { - return new iter(); - } - - private class iter implements Iterator { - NBSI _nbsi2; - int _idx = -1; - int _prev = -1; - - iter() { - _nbsi2 = _nbsi; - advance(); - } - - public boolean hasNext() { - return _idx != -2; - } - - private void advance() { - while (true) { - _idx++; // Next index - while ((_idx >> 6) >= _nbsi2._bits.length) { // Index out of range? - if (_nbsi2._new == null) { // New table? - _idx = -2; // No, so must be all done - return; // - } - _nbsi2 = _nbsi2._new; // Carry on, in the new table - } - if (_nbsi2.contains(_idx)) return; - } - } - - public Integer next() { - if (_idx == -1) throw new NoSuchElementException(); - _prev = _idx; - advance(); - return _prev; - } - - public void remove() { - if (_prev == -1) throw new IllegalStateException(); - _nbsi2.remove(_prev); - _prev = -1; - } - } - - // --- writeObject ------------------------------------------------------- - // Write a NBSI to a stream - - private void writeObject(java.io.ObjectOutputStream s) throws IOException { - s.defaultWriteObject(); // Nothing to write - final NBSI nbsi = _nbsi; // The One Field is transient - final int len = _nbsi._bits.length << 6; - s.writeInt(len); // Write max element - for (int i = 0; i < len; i++) - s.writeBoolean(_nbsi.contains(i)); - } - - // --- readObject -------------------------------------------------------- - // Read a CHM from a stream - - private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { - s.defaultReadObject(); // Read nothing - final int len = s.readInt(); // Read max element - _nbsi = new NBSI(len, new Counter(), this); - for (int i = 0; i < len; i++) // Read all bits - if (s.readBoolean()) - _nbsi.add(i); - } - - // --- NBSI ---------------------------------------------------------------- - - private static final class NBSI { - // Back pointer to the parent wrapper; sorta like make the class non-static - private transient final NonBlockingSetInt _non_blocking_set_int; - - // Used to count elements: a high-performance counter. - private transient final Counter _size; - - // The Bits - private final long _bits[]; - // --- Bits to allow Unsafe access to arrays - 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; - return _Lbase + idx * _Lscale; - } - - private final boolean CAS(int idx, long old, long nnn) { - return _unsafe.compareAndSwapLong(_bits, rawIndex(_bits, idx), old, nnn); - } - - // --- Resize - // The New Table, only set once to non-zero during a resize. - // Must be atomically set. - private NBSI _new; - private static final long _new_offset; - - static { // - Field f = null; - try { - f = NBSI.class.getDeclaredField("_new"); - } catch (java.lang.NoSuchFieldException e) { - } - _new_offset = _unsafe.objectFieldOffset(f); - } - - private final boolean CAS_new(NBSI nnn) { - return _unsafe.compareAndSwapObject(this, _new_offset, null, nnn); - } - - private transient final AtomicInteger _copyIdx; // Used to count bits started copying - private transient final AtomicInteger _copyDone; // Used to count words copied in a resize operation - private transient final int _sum_bits_length; // Sum of all nested _bits.lengths - - private static final long mask(int i) { - return 1L << (i & 63); - } - - // I need 1 free bit out of 64 to allow for resize. I do this by stealing - // the high order bit - but then I need to do something with adding element - // number 63 (and friends). I could use a mod63 function but it's more - // efficient to handle the mod-64 case as an exception. - // - // Every 64th bit is put in it's own recursive bitvector. If the low 6 bits - // are all set, we shift them off and recursively operate on the _nbsi64 set. - private final NBSI _nbsi64; - - private NBSI(int max_elem, Counter ctr, NonBlockingSetInt nonb) { - super(); - _non_blocking_set_int = nonb; - _size = ctr; - _copyIdx = ctr == null ? null : new AtomicInteger(); - _copyDone = ctr == null ? null : new AtomicInteger(); - // The main array of bits - _bits = new long[(int) (((long) max_elem + 63) >>> 6)]; - // Every 64th bit is moved off to it's own subarray, so that the - // sign-bit is free for other purposes - _nbsi64 = ((max_elem + 1) >>> 6) == 0 ? null : new NBSI((max_elem + 1) >>> 6, null, null); - _sum_bits_length = _bits.length + (_nbsi64 == null ? 0 : _nbsi64._sum_bits_length); - } - - // Lower-case 'int' versions - no autoboxing, very fast. - // 'i' is known positive. - - public boolean add(final int i) { - // Check for out-of-range for the current size bit vector. - // If so we need to grow the bit vector. - if ((i >> 6) >= _bits.length) - return install_larger_new_bits(i). // Install larger pile-o-bits (duh) - help_copy().add(i); // Finally, add to the new table - - // Handle every 64th bit via using a nested array - NBSI nbsi = this; // The bit array being added into - int j = i; // The bit index being added - while ((j & 63) == 63) { // Bit 64? (low 6 bits are all set) - nbsi = nbsi._nbsi64; // Recurse - j = j >> 6; // Strip off low 6 bits (all set) - } - - final long mask = mask(j); - long old; - do { - old = nbsi._bits[j >> 6]; // Read old bits - if (old < 0) // Not mutable? - // Not mutable: finish copy of word, and retry on copied word - return help_copy_impl(i).help_copy().add(i); - if ((old & mask) != 0) return false; // Bit is already set? - } while (!nbsi.CAS(j >> 6, old, old | mask)); - _size.add(1); - return true; - } - - public boolean remove(final int i) { - if ((i >> 6) >= _bits.length) // Out of bounds? Not in this array! - return _new == null ? false : help_copy().remove(i); - - // Handle every 64th bit via using a nested array - NBSI nbsi = this; // The bit array being added into - int j = i; // The bit index being added - while ((j & 63) == 63) { // Bit 64? (low 6 bits are all set) - nbsi = nbsi._nbsi64; // Recurse - j = j >> 6; // Strip off low 6 bits (all set) - } - - final long mask = mask(j); - long old; - do { - old = nbsi._bits[j >> 6]; // Read old bits - if (old < 0) // Not mutable? - // Not mutable: finish copy of word, and retry on copied word - return help_copy_impl(i).help_copy().remove(i); - if ((old & mask) == 0) return false; // Bit is already clear? - } while (!nbsi.CAS(j >> 6, old, old & ~mask)); - _size.add(-1); - return true; - } - - public boolean contains(final int i) { - if ((i >> 6) >= _bits.length) // Out of bounds? Not in this array! - return _new == null ? false : help_copy().contains(i); - - // Handle every 64th bit via using a nested array - NBSI nbsi = this; // The bit array being added into - int j = i; // The bit index being added - while ((j & 63) == 63) { // Bit 64? (low 6 bits are all set) - nbsi = nbsi._nbsi64; // Recurse - j = j >> 6; // Strip off low 6 bits (all set) - } - - final long mask = mask(j); - long old = nbsi._bits[j >> 6]; // Read old bits - if (old < 0) // Not mutable? - // Not mutable: finish copy of word, and retry on copied word - return help_copy_impl(i).help_copy().contains(i); - // Yes mutable: test & return bit - return (old & mask) != 0; - } - - public int size() { - return (int) _size.get(); - } - - // Must grow the current array to hold an element of size i - - private NBSI install_larger_new_bits(final int i) { - if (_new == null) { - // Grow by powers of 2, to avoid minor grow-by-1's. - // Note: must grow by exact powers-of-2 or the by-64-bit trick doesn't work right - int sz = (_bits.length << 6) << 1; - // CAS to install a new larger size. Did it work? Did it fail? We - // don't know and don't care. Only One can be installed, so if - // another thread installed a too-small size, we can't help it - we - // must simply install our new larger size as a nested-resize table. - CAS_new(new NBSI(sz, _size, _non_blocking_set_int)); - } - // Return self for 'fluid' programming style - return this; - } - - // Help any top-level NBSI to copy until completed. - // Always return the _new version of *this* NBSI, in case we're nested. - - private NBSI help_copy() { - // Pick some words to help with - but only help copy the top-level NBSI. - // Nested NBSI waits until the top is done before we start helping. - NBSI top_nbsi = _non_blocking_set_int._nbsi; - final int HELP = 8; // Tuning number: how much copy pain are we willing to inflict? - // We "help" by forcing individual bit indices to copy. However, bits - // come in lumps of 64 per word, so we just advance the bit counter by 64's. - int idx = top_nbsi._copyIdx.getAndAdd(64 * HELP); - for (int i = 0; i < HELP; i++) { - int j = idx + i * 64; - j %= (top_nbsi._bits.length << 6); // Limit, wrap to array size; means we retry indices - top_nbsi.help_copy_impl(j); - top_nbsi.help_copy_impl(j + 63); // Also force the nested-by-64 bit - } - - // Top level guy ready to promote? - // Note: WE may not be the top-level guy! - if (top_nbsi._copyDone.get() == top_nbsi._sum_bits_length) - // One shot CAS to promote - it may fail since we are racing; others - // may promote as well - if (_non_blocking_set_int.CAS_nbsi(top_nbsi, top_nbsi._new)) { - //System.out.println("Promote at top level to size "+(_non_blocking_set_int._nbsi._bits.length<<6)); - } - - // Return the new bitvector for 'fluid' programming style - return _new; - } - - // Help copy this one word. State Machine. - // (1) If not "made immutable" in the old array, set the sign bit to make - // it immutable. - // (2) If non-zero in old array & zero in new, CAS new from 0 to copy-of-old - // (3) If non-zero in old array & non-zero in new, CAS old to zero - // (4) Zero in old, new is valid - // At this point, old should be immutable-zero & new has a copy of bits - - private NBSI help_copy_impl(int i) { - // Handle every 64th bit via using a nested array - NBSI old = this; // The bit array being copied from - NBSI nnn = _new; // The bit array being copied to - if (nnn == null) return this; // Promoted already - int j = i; // The bit index being added - while ((j & 63) == 63) { // Bit 64? (low 6 bits are all set) - old = old._nbsi64; // Recurse - nnn = nnn._nbsi64; // Recurse - j = j >> 6; // Strip off low 6 bits (all set) - } - - // Transit from state 1: word is not immutable yet - // Immutable is in bit 63, the sign bit. - long bits = old._bits[j >> 6]; - while (bits >= 0) { // Still in state (1)? - long oldbits = bits; - bits |= mask(63); // Target state of bits: sign-bit means immutable - if (old.CAS(j >> 6, oldbits, bits)) { - if (oldbits == 0) _copyDone.addAndGet(1); - break; // Success - old array word is now immutable - } - bits = old._bits[j >> 6]; // Retry if CAS failed - } - - // Transit from state 2: non-zero in old and zero in new - if (bits != mask(63)) { // Non-zero in old? - long new_bits = nnn._bits[j >> 6]; - if (new_bits == 0) { // New array is still zero - new_bits = bits & ~mask(63); // Desired new value: a mutable copy of bits - // One-shot CAS attempt, no loop, from 0 to non-zero. - // If it fails, somebody else did the copy for us - if (!nnn.CAS(j >> 6, 0, new_bits)) - new_bits = nnn._bits[j >> 6]; // Since it failed, get the new value - assert new_bits != 0; - } - - // Transit from state 3: non-zero in old and non-zero in new - // One-shot CAS attempt, no loop, from non-zero to 0 (but immutable) - if (old.CAS(j >> 6, bits, mask(63))) - _copyDone.addAndGet(1); // One more word finished copying - } - - // Now in state 4: zero (and immutable) in old - - // Return the self bitvector for 'fluid' programming style - return this; - } - - private void print(int d, String msg) { - for (int i = 0; i < d; i++) - System.out.print(" "); - System.out.println(msg); - } - - private void print(int d) { - StringBuffer buf = new StringBuffer(); - buf.append("NBSI - _bits.len="); - NBSI x = this; - while (x != null) { - buf.append(" " + x._bits.length); - x = x._nbsi64; - } - print(d, buf.toString()); - - x = this; - while (x != null) { - for (int i = 0; i < x._bits.length; i++) - System.out.print(Long.toHexString(x._bits[i]) + " "); - x = x._nbsi64; - System.out.println(); - } - - if (_copyIdx.get() != 0 || _copyDone.get() != 0) - print(d, "_copyIdx=" + _copyIdx.get() + " _copyDone=" + _copyDone.get() + " _words_to_cpy=" + _sum_bits_length); - if (_new != null) { - print(d, "__has_new - "); - _new.print(d + 1); - } - } - } -} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/UtilUnsafe.java b/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/UtilUnsafe.java deleted file mode 100644 index e87c7b4a499..00000000000 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/UtilUnsafe.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elastic Search and Shay Banon under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. Elastic Search licenses this - * file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.common.util.concurrent.highscalelib; - -import sun.misc.Unsafe; - -import java.lang.reflect.Field; - -/** - * Simple class to obtain access to the {@link Unsafe} object. {@link Unsafe} - * is required to allow efficient CAS operations on arrays. Note that the - * versions in {@link java.util.concurrent.atomic}, such as {@link - * java.util.concurrent.atomic.AtomicLongArray}, require extra memory ordering - * guarantees which are generally not needed in these algorithms and are also - * expensive on most processors. - */ -class UtilUnsafe { - private UtilUnsafe() { - } // dummy private constructor - - /** - * Fetch the Unsafe. Use With Caution. - */ - public static Unsafe getUnsafe() { - // Not on bootclasspath - if (UtilUnsafe.class.getClassLoader() == null) - return Unsafe.getUnsafe(); - try { - final Field fld = Unsafe.class.getDeclaredField("theUnsafe"); - fld.setAccessible(true); - return (Unsafe) fld.get(UtilUnsafe.class); - } catch (Exception e) { - throw new RuntimeException("Could not obtain access to sun.misc.Unsafe", e); - } - } -} diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/package-info.java b/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/package-info.java deleted file mode 100644 index 3d6a84d0294..00000000000 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/common/util/concurrent/highscalelib/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to Elastic Search and Shay Banon under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. Elastic Search licenses this - * file to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * A copy of Cliff High-Scale-Lib version 1.1.2. - */ -package org.elasticsearch.common.util.concurrent.highscalelib; \ No newline at end of file