/*
* Written by Cliff Click and released to the public domain, as explained at
* http://creativecommons.org/licenses/publicdomain
*/
package org.cliffc.high_scale_lib;
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;
import sun.misc.Unsafe;
/**
* 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.
*
* <p><em>General note of caution:</em> 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.
*
* <p>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.
*
* @since 1.5
* @author Cliff Click
*/
public class NonBlockingSetInt extends AbstractSet<Integer> 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 { // <clinit>
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.
* @throws IllegalArgumentException if i is negative.
* @return <tt>true</tt> if i was added to the set.
*/
@Override
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 <tt>true</tt> if i was in the set.
*/
@Override
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 <tt>true</tt> if i was removed to the set.
*/
@Override
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.
* @throws IllegalArgumentException if i is negative.
* @return <tt>true</tt> if i was added to the set.
*/
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 <tt>true</tt> 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 <tt>true</tt> 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.
*/
@Override
public int size() {
return _nbsi.size();
}
/** Empty the bitvector. */
@Override
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.
*/
@Override
public Iterator<Integer> iterator() {
return new iter();
}
private class iter implements Iterator<Integer> {
NBSI _nbsi2;
int _idx = -1;
int _prev = -1;
iter() {
_nbsi2 = _nbsi;
advance();
}
@Override
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;
}
}
}
@Override
public Integer next() {
if (_idx == -1) {
throw new NoSuchElementException();
}
_prev = _idx;
advance();
return _prev;
}
@Override
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 { // <clinit>
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);
}
}
}
}