Added jctools library, cleaned up. Slow performance for 4x4??
This commit is contained in:
parent
1a591165a8
commit
b967733a63
@ -5,12 +5,13 @@ import java.util.ArrayDeque;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.jctools.util.Pow2;
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.DeadMessage;
|
import dorkbox.util.messagebus.common.DeadMessage;
|
||||||
import dorkbox.util.messagebus.common.ISetEntry;
|
import dorkbox.util.messagebus.common.ISetEntry;
|
||||||
import dorkbox.util.messagebus.common.NamedThreadFactory;
|
import dorkbox.util.messagebus.common.NamedThreadFactory;
|
||||||
import dorkbox.util.messagebus.common.StrongConcurrentSetV8;
|
import dorkbox.util.messagebus.common.StrongConcurrentSetV8;
|
||||||
import dorkbox.util.messagebus.common.simpleq.jctools.MpmcTransferArrayQueue;
|
import dorkbox.util.messagebus.common.simpleq.jctools.MpmcTransferArrayQueue;
|
||||||
import dorkbox.util.messagebus.common.simpleq.jctools.Pow2;
|
|
||||||
import dorkbox.util.messagebus.error.IPublicationErrorHandler;
|
import dorkbox.util.messagebus.error.IPublicationErrorHandler;
|
||||||
import dorkbox.util.messagebus.error.PublicationError;
|
import dorkbox.util.messagebus.error.PublicationError;
|
||||||
import dorkbox.util.messagebus.subscription.Subscription;
|
import dorkbox.util.messagebus.subscription.Subscription;
|
||||||
|
@ -1,155 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed 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 dorkbox.util.messagebus.common.simpleq.jctools;
|
|
||||||
|
|
||||||
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.jctools.UnsafeAccess.UNSAFE;
|
|
||||||
|
|
||||||
import java.util.AbstractQueue;
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
abstract class ConcurrentCircularArrayQueueL0Pad<E> extends AbstractQueue<E> implements MessagePassingQueue<E> {
|
|
||||||
long p00, p01, p02, p03, p04, p05, p06, p07;
|
|
||||||
long p30, p31, p32, p33, p34, p35, p36, p37;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A concurrent access enabling class used by circular array based queues this class exposes an offset computation
|
|
||||||
* method along with differently memory fenced load/store methods into the underlying array. The class is pre-padded and
|
|
||||||
* the array is padded on either side to help with False sharing prevention. It is expected that subclasses handle post
|
|
||||||
* padding.
|
|
||||||
* <p>
|
|
||||||
* Offset calculation is separate from access to enable the reuse of a give compute offset.
|
|
||||||
* <p>
|
|
||||||
* Load/Store methods using a <i>buffer</i> parameter are provided to allow the prevention of final field reload after a
|
|
||||||
* LoadLoad barrier.
|
|
||||||
* <p>
|
|
||||||
*
|
|
||||||
* @author nitsanw
|
|
||||||
*
|
|
||||||
* @param <E>
|
|
||||||
*/
|
|
||||||
public abstract class ConcurrentCircularArrayQueue<E> extends ConcurrentCircularArrayQueueL0Pad<E> {
|
|
||||||
protected static final int SPARSE_SHIFT = Integer.getInteger("sparse.shift", 0);
|
|
||||||
protected static final int BUFFER_PAD;
|
|
||||||
private static final long REF_ARRAY_BASE;
|
|
||||||
private static final int REF_ELEMENT_SHIFT;
|
|
||||||
|
|
||||||
static {
|
|
||||||
final int scale = UnsafeAccess.UNSAFE.arrayIndexScale(Object[].class);
|
|
||||||
if (4 == scale) {
|
|
||||||
REF_ELEMENT_SHIFT = 2 + SPARSE_SHIFT;
|
|
||||||
} else if (8 == scale) {
|
|
||||||
REF_ELEMENT_SHIFT = 3 + SPARSE_SHIFT;
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("Unknown pointer size");
|
|
||||||
}
|
|
||||||
|
|
||||||
BUFFER_PAD = 128 / scale;
|
|
||||||
// Including the buffer pad in the array base offset
|
|
||||||
REF_ARRAY_BASE = UnsafeAccess.UNSAFE.arrayBaseOffset(Object[].class)
|
|
||||||
+ (BUFFER_PAD << REF_ELEMENT_SHIFT - SPARSE_SHIFT);
|
|
||||||
}
|
|
||||||
protected final long mask;
|
|
||||||
// @Stable :(
|
|
||||||
protected final E[] buffer;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public ConcurrentCircularArrayQueue(int capacity) {
|
|
||||||
int actualCapacity = Pow2.roundToPowerOfTwo(capacity);
|
|
||||||
this.mask = actualCapacity - 1;
|
|
||||||
// pad data on either end with some empty slots.
|
|
||||||
this.buffer = (E[]) new Object[(actualCapacity << SPARSE_SHIFT) + BUFFER_PAD * 2];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param index desirable element index
|
|
||||||
* @return the offset in bytes within the array for a given index.
|
|
||||||
*/
|
|
||||||
protected final long calcElementOffset(long index) {
|
|
||||||
return calcElementOffset(index, this.mask);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @param index desirable element index
|
|
||||||
* @param mask
|
|
||||||
* @return the offset in bytes within the array for a given index.
|
|
||||||
*/
|
|
||||||
protected static final long calcElementOffset(long index, long mask) {
|
|
||||||
return REF_ARRAY_BASE + ((index & mask) << REF_ELEMENT_SHIFT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A plain store (no ordering/fences) of an element to a given offset
|
|
||||||
*
|
|
||||||
* @param offset computed via {@link ConcurrentCircularArrayQueue#calcElementOffset(long)}
|
|
||||||
* @param e a kitty
|
|
||||||
*/
|
|
||||||
protected final void spElement(long offset, Object e) {
|
|
||||||
UNSAFE.putObject(this.buffer, offset, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An ordered store(store + StoreStore barrier) of an element to a given offset
|
|
||||||
*
|
|
||||||
* @param offset computed via {@link ConcurrentCircularArrayQueue#calcElementOffset(long)}
|
|
||||||
* @param e an orderly kitty
|
|
||||||
*/
|
|
||||||
protected final void soElement(long offset, E e) {
|
|
||||||
UNSAFE.putOrderedObject(this.buffer, offset, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A plain load (no ordering/fences) of an element from a given offset.
|
|
||||||
*
|
|
||||||
* @param offset computed via {@link ConcurrentCircularArrayQueue#calcElementOffset(long)}
|
|
||||||
* @return the element at the offset
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected final E lpElement(long offset) {
|
|
||||||
return (E) UNSAFE.getObject(this.buffer, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A plain load (no ordering/fences) of an element from a given offset.
|
|
||||||
*
|
|
||||||
* @param offset computed via {@link ConcurrentCircularArrayQueue#calcElementOffset(long)}
|
|
||||||
* @return the element at the offset
|
|
||||||
*/
|
|
||||||
protected final Object lpElementNoCast(long offset) {
|
|
||||||
return UNSAFE.getObject(this.buffer, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A volatile load (load + LoadLoad barrier) of an element from a given offset.
|
|
||||||
*
|
|
||||||
* @param offset computed via {@link ConcurrentCircularArrayQueue#calcElementOffset(long)}
|
|
||||||
* @return the element at the offset
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected final E lvElement(long offset) {
|
|
||||||
return (E) UNSAFE.getObjectVolatile(this.buffer, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterator<E> iterator() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void clear() {
|
|
||||||
// we have to test isEmpty because of the weaker poll() guarantee
|
|
||||||
while (poll() != null || !isEmpty()) {
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed 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 dorkbox.util.messagebus.common.simpleq.jctools;
|
|
||||||
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.jctools.UnsafeAccess.UNSAFE;
|
|
||||||
|
|
||||||
public abstract class ConcurrentSequencedCircularArrayQueue<E> extends ConcurrentCircularArrayQueue<E> {
|
|
||||||
private static final long ARRAY_BASE;
|
|
||||||
private static final int ELEMENT_SHIFT;
|
|
||||||
static {
|
|
||||||
final int scale = UnsafeAccess.UNSAFE.arrayIndexScale(long[].class);
|
|
||||||
if (8 == scale) {
|
|
||||||
ELEMENT_SHIFT = 3 + SPARSE_SHIFT;
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("Unexpected long[] element size");
|
|
||||||
}
|
|
||||||
// Including the buffer pad in the array base offset
|
|
||||||
ARRAY_BASE = UnsafeAccess.UNSAFE.arrayBaseOffset(long[].class) + (BUFFER_PAD << ELEMENT_SHIFT - SPARSE_SHIFT);
|
|
||||||
}
|
|
||||||
protected final long[] sequenceBuffer;
|
|
||||||
|
|
||||||
public ConcurrentSequencedCircularArrayQueue(int capacity) {
|
|
||||||
super(capacity);
|
|
||||||
int actualCapacity = (int) (this.mask + 1);
|
|
||||||
// pad data on either end with some empty slots.
|
|
||||||
this.sequenceBuffer = new long[(actualCapacity << SPARSE_SHIFT) + BUFFER_PAD * 2];
|
|
||||||
for (long i = 0; i < actualCapacity; i++) {
|
|
||||||
soSequence(this.sequenceBuffer, calcSequenceOffset(i), i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final long calcSequenceOffset(long index) {
|
|
||||||
return calcSequenceOffset(index, this.mask);
|
|
||||||
}
|
|
||||||
protected static final long calcSequenceOffset(long index, long mask) {
|
|
||||||
return ARRAY_BASE + ((index & mask) << ELEMENT_SHIFT);
|
|
||||||
}
|
|
||||||
protected final void soSequence(long[] buffer, long offset, long e) {
|
|
||||||
UNSAFE.putOrderedLong(buffer, offset, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final long lvSequence(long[] buffer, long offset) {
|
|
||||||
return UNSAFE.getLongVolatile(buffer, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed 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 dorkbox.util.messagebus.common.simpleq.jctools;
|
|
||||||
|
|
||||||
import java.util.Queue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a tagging interface for the queues in this library which implement a subset of the {@link Queue} interface
|
|
||||||
* sufficient for concurrent message passing.<br>
|
|
||||||
* Message passing queues offer happens before semantics to messages passed through, namely that writes made by the
|
|
||||||
* producer before offering the message are visible to the consuming thread after the message has been polled out of the
|
|
||||||
* queue.
|
|
||||||
*
|
|
||||||
* @author nitsanw
|
|
||||||
*
|
|
||||||
* @param <M> the event/message type
|
|
||||||
*/
|
|
||||||
interface MessagePassingQueue<M> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called from a producer thread subject to the restrictions appropriate to the implementation and according to the
|
|
||||||
* {@link Queue#offer(Object)} interface.
|
|
||||||
*
|
|
||||||
* @param message
|
|
||||||
* @return true if element was inserted into the queue, false iff full
|
|
||||||
*/
|
|
||||||
boolean offer(M message);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called from the consumer thread subject to the restrictions appropriate to the implementation and according to
|
|
||||||
* the {@link Queue#poll()} interface.
|
|
||||||
*
|
|
||||||
* @return a message from the queue if one is available, null iff empty
|
|
||||||
*/
|
|
||||||
M poll();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called from the consumer thread subject to the restrictions appropriate to the implementation and according to
|
|
||||||
* the {@link Queue#peek()} interface.
|
|
||||||
*
|
|
||||||
* @return a message from the queue if one is available, null iff empty
|
|
||||||
*/
|
|
||||||
M peek();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method's accuracy is subject to concurrent modifications happening as the size is estimated and as such is a
|
|
||||||
* best effort rather than absolute value. For some implementations this method may be O(n) rather than O(1).
|
|
||||||
*
|
|
||||||
* @return number of messages in the queue, between 0 and queue capacity or {@link Integer#MAX_VALUE} if not bounded
|
|
||||||
*/
|
|
||||||
int size();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method's accuracy is subject to concurrent modifications happening as the observation is carried out.
|
|
||||||
*
|
|
||||||
* @return true if empty, false otherwise
|
|
||||||
*/
|
|
||||||
boolean isEmpty();
|
|
||||||
|
|
||||||
}
|
|
@ -1,214 +0,0 @@
|
|||||||
/*
|
|
||||||
* Licensed 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 dorkbox.util.messagebus.common.simpleq.jctools;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Multi-Producer-Multi-Consumer queue based on a {@link ConcurrentCircularArrayQueue}. This implies that
|
|
||||||
* any and all threads may call the offer/poll/peek methods and correctness is maintained. <br>
|
|
||||||
* This implementation follows patterns documented on the package level for False Sharing protection.<br>
|
|
||||||
* The algorithm for offer/poll is an adaptation of the one put forward by D. Vyukov (See <a
|
|
||||||
* href="http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue">here</a>). The original
|
|
||||||
* algorithm uses an array of structs which should offer nice locality properties but is sadly not possible in
|
|
||||||
* Java (waiting on Value Types or similar). The alternative explored here utilizes 2 arrays, one for each
|
|
||||||
* field of the struct. There is a further alternative in the experimental project which uses iteration phase
|
|
||||||
* markers to achieve the same algo and is closer structurally to the original, but sadly does not perform as
|
|
||||||
* well as this implementation.<br>
|
|
||||||
* Tradeoffs to keep in mind:
|
|
||||||
* <ol>
|
|
||||||
* <li>Padding for false sharing: counter fields and queue fields are all padded as well as either side of
|
|
||||||
* both arrays. We are trading memory to avoid false sharing(active and passive).
|
|
||||||
* <li>2 arrays instead of one: The algorithm requires an extra array of longs matching the size of the
|
|
||||||
* elements array. This is doubling/tripling the memory allocated for the buffer.
|
|
||||||
* <li>Power of 2 capacity: Actual elements buffer (and sequence buffer) is the closest power of 2 larger or
|
|
||||||
* equal to the requested capacity.
|
|
||||||
* </ol>
|
|
||||||
*/
|
|
||||||
public class MpmcArrayQueue<T> extends MpmcArrayQueueConsumerField<T> {
|
|
||||||
/** The number of CPUs */
|
|
||||||
private static final int NCPU = Runtime.getRuntime().availableProcessors();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of times to spin (doing nothing except polling a memory location) before giving up while waiting to eliminate an
|
|
||||||
* operation. Should be zero on uniprocessors. On multiprocessors, this value should be large enough so that two threads exchanging
|
|
||||||
* items as fast as possible block only when one of them is stalled (due to GC or preemption), but not much longer, to avoid wasting CPU
|
|
||||||
* resources. Seen differently, this value is a little over half the number of cycles of an average context switch time on most systems.
|
|
||||||
* The value here is approximately the average of those across a range of tested systems.
|
|
||||||
*/
|
|
||||||
private static final int SPINS = NCPU == 1 ? 0 : 600;
|
|
||||||
|
|
||||||
long p40, p41, p42, p43, p44, p45, p46;
|
|
||||||
long p30, p31, p32, p33, p34, p35, p36, p37;
|
|
||||||
|
|
||||||
public MpmcArrayQueue(final int capacity) {
|
|
||||||
super(validateCapacity(capacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int validateCapacity(int capacity) {
|
|
||||||
if(capacity < 2) {
|
|
||||||
throw new IllegalArgumentException("Minimum size is 2");
|
|
||||||
}
|
|
||||||
return capacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean offer(final T e) {
|
|
||||||
// local load of field to avoid repeated loads after volatile reads
|
|
||||||
final long mask = this.mask;
|
|
||||||
final long capacity = mask + 1;
|
|
||||||
final long[] sBuffer = this.sequenceBuffer;
|
|
||||||
|
|
||||||
long producerIndex;
|
|
||||||
long pSeqOffset;
|
|
||||||
long consumerIndex = Long.MAX_VALUE;// start with bogus value, hope we don't need it
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
producerIndex = lvProducerIndex(); // LoadLoad
|
|
||||||
pSeqOffset = calcSequenceOffset(producerIndex, mask);
|
|
||||||
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
|
|
||||||
final long delta = seq - producerIndex;
|
|
||||||
|
|
||||||
if (delta == 0) {
|
|
||||||
// this is expected if we see this first time around
|
|
||||||
if (casProducerIndex(producerIndex, producerIndex + 1)) {
|
|
||||||
// Successful CAS: full barrier
|
|
||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
|
||||||
final long offset = calcElementOffset(producerIndex, mask);
|
|
||||||
spElement(offset, e);
|
|
||||||
|
|
||||||
// increment sequence by 1, the value expected by consumer
|
|
||||||
// (seeing this value from a producer will lead to retry 2)
|
|
||||||
soSequence(sBuffer, pSeqOffset, producerIndex + 1); // StoreStore
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// failed cas, retry 1
|
|
||||||
} else if (delta < 0 && // poll has not moved this value forward
|
|
||||||
producerIndex - capacity <= consumerIndex && // test against cached cIndex
|
|
||||||
producerIndex - capacity <= (consumerIndex = lvConsumerIndex())) { // test against latest cIndex
|
|
||||||
// Extra check required to ensure [Queue.offer == false iff queue is full]
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// another producer has moved the sequence by one, retry 2
|
|
||||||
busySpin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
* <p>
|
|
||||||
* Because return null indicates queue is empty we cannot simply rely on next element visibility for poll
|
|
||||||
* and must test producer index when next element is not visible.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public T poll() {
|
|
||||||
// local load of field to avoid repeated loads after volatile reads
|
|
||||||
final long mask = this.mask;
|
|
||||||
final long[] sBuffer = this.sequenceBuffer;
|
|
||||||
|
|
||||||
long consumerIndex;
|
|
||||||
long cSeqOffset;
|
|
||||||
long producerIndex = -1; // start with bogus value, hope we don't need it
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
consumerIndex = lvConsumerIndex(); // LoadLoad
|
|
||||||
cSeqOffset = calcSequenceOffset(consumerIndex, mask);
|
|
||||||
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
|
|
||||||
final long delta = seq - (consumerIndex + 1);
|
|
||||||
|
|
||||||
if (delta == 0) {
|
|
||||||
if (casConsumerIndex(consumerIndex, consumerIndex + 1)) {
|
|
||||||
// Successful CAS: full barrier
|
|
||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
|
||||||
final long offset = calcElementOffset(consumerIndex, mask);
|
|
||||||
final T e = lpElement(offset);
|
|
||||||
spElement(offset, null);
|
|
||||||
|
|
||||||
// Move sequence ahead by capacity, preparing it for next offer
|
|
||||||
// (seeing this value from a consumer will lead to retry 2)
|
|
||||||
soSequence(sBuffer, cSeqOffset, consumerIndex + mask + 1); // StoreStore
|
|
||||||
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
// failed cas, retry 1
|
|
||||||
} else if (delta < 0 && // slot has not been moved by producer
|
|
||||||
consumerIndex >= producerIndex && // test against cached pIndex
|
|
||||||
consumerIndex == (producerIndex = lvProducerIndex())) { // update pIndex if we must
|
|
||||||
// strict empty check, this ensures [Queue.poll() == null iff isEmpty()]
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// another consumer beat us and moved sequence ahead, retry 2
|
|
||||||
// only producer will busy spin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T peek() {
|
|
||||||
long currConsumerIndex;
|
|
||||||
T e;
|
|
||||||
do {
|
|
||||||
currConsumerIndex = lvConsumerIndex();
|
|
||||||
// other consumers may have grabbed the element, or queue might be empty
|
|
||||||
e = lpElement(calcElementOffset(currConsumerIndex));
|
|
||||||
// only return null if queue is empty
|
|
||||||
} while (e == null && currConsumerIndex != lvProducerIndex());
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
/*
|
|
||||||
* It is possible for a thread to be interrupted or reschedule between the read of the producer and
|
|
||||||
* consumer indices, therefore protection is required to ensure size is within valid range. In the
|
|
||||||
* event of concurrent polls/offers to this method the size is OVER estimated as we read consumer
|
|
||||||
* index BEFORE the producer index.
|
|
||||||
*/
|
|
||||||
long after = lvConsumerIndex();
|
|
||||||
while (true) {
|
|
||||||
final long before = after;
|
|
||||||
final long currentProducerIndex = lvProducerIndex();
|
|
||||||
after = lvConsumerIndex();
|
|
||||||
if (before == after) {
|
|
||||||
return (int) (currentProducerIndex - after);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
// Order matters!
|
|
||||||
// Loading consumer before producer allows for producer increments after consumer index is read.
|
|
||||||
// This ensures this method is conservative in it's estimate. Note that as this is an MPMC there is
|
|
||||||
// nothing we can do to make this an exact method.
|
|
||||||
return lvConsumerIndex() == lvProducerIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final void busySpin() {
|
|
||||||
// busy spin for the amount of time (roughly) of a CPU context switch
|
|
||||||
int spins = SPINS;
|
|
||||||
for (;;) {
|
|
||||||
if (spins > 0) {
|
|
||||||
--spins;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package dorkbox.util.messagebus.common.simpleq.jctools;
|
|
||||||
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.jctools.UnsafeAccess.UNSAFE;
|
|
||||||
|
|
||||||
public abstract class MpmcArrayQueueConsumerField<E> extends MpmcArrayQueueL2Pad<E> {
|
|
||||||
private final static long C_INDEX_OFFSET;
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
C_INDEX_OFFSET = UNSAFE.objectFieldOffset(MpmcArrayQueueConsumerField.class.getDeclaredField("consumerIndex"));
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private volatile long consumerIndex;
|
|
||||||
|
|
||||||
public MpmcArrayQueueConsumerField(int capacity) {
|
|
||||||
super(capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final long lvConsumerIndex() {
|
|
||||||
return this.consumerIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final long lpConsumerIndex() {
|
|
||||||
return UNSAFE.getLong(this, C_INDEX_OFFSET);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final boolean casConsumerIndex(long expect, long newValue) {
|
|
||||||
return UNSAFE.compareAndSwapLong(this, C_INDEX_OFFSET, expect, newValue);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package dorkbox.util.messagebus.common.simpleq.jctools;
|
|
||||||
|
|
||||||
abstract class MpmcArrayQueueL1Pad<E> extends ConcurrentSequencedCircularArrayQueue<E> {
|
|
||||||
long p10, p11, p12, p13, p14, p15, p16;
|
|
||||||
long p30, p31, p32, p33, p34, p35, p36, p37;
|
|
||||||
|
|
||||||
public MpmcArrayQueueL1Pad(int capacity) {
|
|
||||||
super(capacity);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package dorkbox.util.messagebus.common.simpleq.jctools;
|
|
||||||
|
|
||||||
abstract class MpmcArrayQueueL2Pad<E> extends MpmcArrayQueueProducerField<E> {
|
|
||||||
long p20, p21, p22, p23, p24, p25, p26;
|
|
||||||
long p30, p31, p32, p33, p34, p35, p36, p37;
|
|
||||||
|
|
||||||
public MpmcArrayQueueL2Pad(int capacity) {
|
|
||||||
super(capacity);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package dorkbox.util.messagebus.common.simpleq.jctools;
|
|
||||||
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.jctools.UnsafeAccess.UNSAFE;
|
|
||||||
|
|
||||||
abstract class MpmcArrayQueueProducerField<E> extends MpmcArrayQueueL1Pad<E> {
|
|
||||||
private final static long P_INDEX_OFFSET;
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
P_INDEX_OFFSET = UNSAFE.objectFieldOffset(MpmcArrayQueueProducerField.class
|
|
||||||
.getDeclaredField("producerIndex"));
|
|
||||||
} catch (NoSuchFieldException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private volatile long producerIndex;
|
|
||||||
|
|
||||||
public MpmcArrayQueueProducerField(int capacity) {
|
|
||||||
super(capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final long lvProducerIndex() {
|
|
||||||
return this.producerIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final boolean casProducerIndex(long expect, long newValue) {
|
|
||||||
return UNSAFE.compareAndSwapLong(this, P_INDEX_OFFSET, expect, newValue);
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,15 +10,18 @@ import static dorkbox.util.messagebus.common.simpleq.jctools.Node.soThread;
|
|||||||
import static dorkbox.util.messagebus.common.simpleq.jctools.Node.spItem1;
|
import static dorkbox.util.messagebus.common.simpleq.jctools.Node.spItem1;
|
||||||
import static dorkbox.util.messagebus.common.simpleq.jctools.Node.spThread;
|
import static dorkbox.util.messagebus.common.simpleq.jctools.Node.spThread;
|
||||||
import static dorkbox.util.messagebus.common.simpleq.jctools.Node.spType;
|
import static dorkbox.util.messagebus.common.simpleq.jctools.Node.spType;
|
||||||
import static dorkbox.util.messagebus.common.simpleq.jctools.UnsafeAccess.UNSAFE;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TransferQueue;
|
import java.util.concurrent.TransferQueue;
|
||||||
|
|
||||||
|
import org.jctools.queues.MpmcArrayQueue;
|
||||||
|
import org.jctools.util.Pow2;
|
||||||
|
import org.jctools.util.UnsafeAccess;
|
||||||
|
|
||||||
public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Object> implements TransferQueue<Object> {
|
|
||||||
|
public final class MpmcTransferArrayQueue extends MpmcArrayQueue<Object> implements TransferQueue<Object> {
|
||||||
private static final int TYPE_EMPTY = 0;
|
private static final int TYPE_EMPTY = 0;
|
||||||
private static final int TYPE_CONSUMER = 1;
|
private static final int TYPE_CONSUMER = 1;
|
||||||
private static final int TYPE_PRODUCER = 2;
|
private static final int TYPE_PRODUCER = 2;
|
||||||
@ -79,19 +82,127 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final void transfer(final Object item) {
|
public final void transfer(final Object item) {
|
||||||
producerWait(item, false, 0L);
|
producerXfer(item, false, 0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CONSUMER
|
* CONSUMER
|
||||||
|
* <p>
|
||||||
|
* Remove an item from the queue. If there are no items on the queue, wait for a producer to place an item on the queue. This will
|
||||||
|
* as long as necessary
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final Object take() {
|
public final Object take() {
|
||||||
return consumerWait(false, 0L);
|
// local load of field to avoid repeated loads after volatile reads
|
||||||
|
final long mask = this.mask;
|
||||||
|
final long[] sBuffer = this.sequenceBuffer;
|
||||||
|
|
||||||
|
long consumerIndex;
|
||||||
|
long producerIndex;
|
||||||
|
int lastType;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
consumerIndex = lvConsumerIndex();
|
||||||
|
producerIndex = lvProducerIndex();
|
||||||
|
|
||||||
|
final Object previousElement;
|
||||||
|
if (consumerIndex == producerIndex) {
|
||||||
|
lastType = TYPE_EMPTY;
|
||||||
|
previousElement = null;
|
||||||
|
} else {
|
||||||
|
previousElement = lpElement(calcElementOffset(producerIndex-1));
|
||||||
|
if (previousElement == null) {
|
||||||
|
// the last producer hasn't finished setting the object yet
|
||||||
|
busySpin(INPROGRESS_SPINS);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastType = lpType(previousElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (lastType) {
|
||||||
|
case TYPE_EMPTY:
|
||||||
|
case TYPE_CONSUMER: {
|
||||||
|
|
||||||
|
// if (lastType != TYPE_PRODUCER) {
|
||||||
|
// TYPE_EMPTY, TYPE_CONSUMER
|
||||||
|
// empty or same mode = push+park onto queue
|
||||||
|
long pSeqOffset = calcSequenceOffset(producerIndex, mask);
|
||||||
|
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
|
||||||
|
final long delta = seq - producerIndex;
|
||||||
|
|
||||||
|
if (delta == 0) {
|
||||||
|
// this is expected if we see this first time around
|
||||||
|
if (casProducerIndex(producerIndex, producerIndex + 1)) {
|
||||||
|
// Successful CAS: full barrier
|
||||||
|
|
||||||
|
final Thread myThread = Thread.currentThread();
|
||||||
|
final Object node = nodeThreadLocal.get();
|
||||||
|
|
||||||
|
spType(node, TYPE_CONSUMER);
|
||||||
|
spThread(node, myThread);
|
||||||
|
|
||||||
|
|
||||||
|
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
||||||
|
final long offset = calcElementOffset(producerIndex, mask);
|
||||||
|
spElement(offset, node);
|
||||||
|
|
||||||
|
|
||||||
|
// increment sequence by 1, the value expected by consumer
|
||||||
|
// (seeing this value from a producer will lead to retry 2)
|
||||||
|
soSequence(sBuffer, pSeqOffset, producerIndex + 1); // StoreStore
|
||||||
|
|
||||||
|
park(node, myThread, false, 0L);
|
||||||
|
Object item1 = lvItem1(node);
|
||||||
|
|
||||||
|
return item1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// whoops, inconsistent state
|
||||||
|
busySpin(PUSH_SPINS);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// else {
|
||||||
|
case TYPE_PRODUCER: {
|
||||||
|
// TYPE_PRODUCER
|
||||||
|
// complimentary mode = pop+unpark off queue
|
||||||
|
long cSeqOffset = calcSequenceOffset(consumerIndex, mask);
|
||||||
|
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
|
||||||
|
final long delta = seq - (consumerIndex + 1);
|
||||||
|
|
||||||
|
if (delta == 0) {
|
||||||
|
if (casConsumerIndex(consumerIndex, consumerIndex + 1)) {
|
||||||
|
// Successful CAS: full barrier
|
||||||
|
|
||||||
|
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
||||||
|
final long offset = calcElementOffset(consumerIndex, mask);
|
||||||
|
final Object e = lpElement(offset);
|
||||||
|
spElement(offset, null);
|
||||||
|
|
||||||
|
// Move sequence ahead by capacity, preparing it for next offer
|
||||||
|
// (seeing this value from a consumer will lead to retry 2)
|
||||||
|
soSequence(sBuffer, cSeqOffset, consumerIndex + mask + 1); // StoreStore
|
||||||
|
|
||||||
|
final Object lvItem1 = lpItem1(e);
|
||||||
|
unpark(e);
|
||||||
|
|
||||||
|
return lvItem1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// whoops, inconsistent state
|
||||||
|
busySpin(POP_SPINS);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// modification of super implementation, as to include a small busySpin on contention
|
||||||
@Override
|
@Override
|
||||||
public boolean offer(Object item) {
|
public final boolean offer(Object item) {
|
||||||
// local load of field to avoid repeated loads after volatile reads
|
// local load of field to avoid repeated loads after volatile reads
|
||||||
final long mask = this.mask;
|
final long mask = this.mask;
|
||||||
final long capacity = mask + 1;
|
final long capacity = mask + 1;
|
||||||
@ -137,6 +248,9 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will busy spin until timeout
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean offer(Object item, long timeout, TimeUnit unit) throws InterruptedException {
|
public boolean offer(Object item, long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
long nanos = unit.toNanos(timeout);
|
long nanos = unit.toNanos(timeout);
|
||||||
@ -188,7 +302,7 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
if (remaining < SPIN_THRESHOLD) {
|
if (remaining < SPIN_THRESHOLD) {
|
||||||
busySpin(PARK_UNTIMED_SPINS);
|
busySpin(PARK_UNTIMED_SPINS);
|
||||||
} else {
|
} else {
|
||||||
UNSAFE.park(false, 1L);
|
UnsafeAccess.UNSAFE.park(false, 1L);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@ -251,7 +365,7 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// modification of super implementation, as to include a small busySpin on contention
|
||||||
@Override
|
@Override
|
||||||
public Object poll() {
|
public Object poll() {
|
||||||
// local load of field to avoid repeated loads after volatile reads
|
// local load of field to avoid repeated loads after volatile reads
|
||||||
@ -274,7 +388,7 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
||||||
final long offset = calcElementOffset(consumerIndex, mask);
|
final long offset = calcElementOffset(consumerIndex, mask);
|
||||||
final Object e = lpElementNoCast(offset);
|
final Object e = lpElement(offset);
|
||||||
spElement(offset, null);
|
spElement(offset, null);
|
||||||
|
|
||||||
// Move sequence ahead by capacity, preparing it for next offer
|
// Move sequence ahead by capacity, preparing it for next offer
|
||||||
@ -312,7 +426,7 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
do {
|
do {
|
||||||
currConsumerIndex = lvConsumerIndex();
|
currConsumerIndex = lvConsumerIndex();
|
||||||
// other consumers may have grabbed the element, or queue might be empty
|
// other consumers may have grabbed the element, or queue might be empty
|
||||||
e = lpElementNoCast(calcElementOffset(currConsumerIndex));
|
e = lpElement(calcElementOffset(currConsumerIndex));
|
||||||
// only return null if queue is empty
|
// only return null if queue is empty
|
||||||
} while (e == null && currConsumerIndex != lvProducerIndex());
|
} while (e == null && currConsumerIndex != lvProducerIndex());
|
||||||
return e;
|
return e;
|
||||||
@ -356,7 +470,7 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
lastType = TYPE_EMPTY;
|
lastType = TYPE_EMPTY;
|
||||||
previousElement = null;
|
previousElement = null;
|
||||||
} else {
|
} else {
|
||||||
previousElement = lpElementNoCast(calcElementOffset(producerIndex-1));
|
previousElement = lpElement(calcElementOffset(producerIndex-1));
|
||||||
if (previousElement == null) {
|
if (previousElement == null) {
|
||||||
// the last producer hasn't finished setting the object yet
|
// the last producer hasn't finished setting the object yet
|
||||||
busySpin(INPROGRESS_SPINS);
|
busySpin(INPROGRESS_SPINS);
|
||||||
@ -366,23 +480,22 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
lastType = lpType(previousElement);
|
lastType = lpType(previousElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (lastType) {
|
if (lastType != TYPE_CONSUMER) {
|
||||||
case TYPE_EMPTY:
|
// TYPE_EMPTY, TYPE_PRODUCER
|
||||||
case TYPE_PRODUCER: {
|
|
||||||
// empty or same mode = push+park onto queue
|
// empty or same mode = push+park onto queue
|
||||||
long pSeqOffset = calcSequenceOffset(producerIndex, mask);
|
long pSeqOffset = calcSequenceOffset(producerIndex, mask);
|
||||||
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
|
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
|
||||||
final long delta = seq - producerIndex;
|
final long delta = seq - producerIndex;
|
||||||
|
|
||||||
if (delta == 0) {
|
if (delta == 0) {
|
||||||
|
// don't add to queue, abort!!
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// whoops, inconsistent state
|
// whoops, inconsistent state
|
||||||
busySpin(PUSH_SPINS);
|
busySpin(PUSH_SPINS);
|
||||||
continue;
|
} else {
|
||||||
}
|
// TYPE_CONSUMER
|
||||||
case TYPE_CONSUMER: {
|
|
||||||
// complimentary mode = pop+unpark off queue
|
// complimentary mode = pop+unpark off queue
|
||||||
long cSeqOffset = calcSequenceOffset(consumerIndex, mask);
|
long cSeqOffset = calcSequenceOffset(consumerIndex, mask);
|
||||||
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
|
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
|
||||||
@ -394,7 +507,7 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
||||||
final long offset = calcElementOffset(consumerIndex, mask);
|
final long offset = calcElementOffset(consumerIndex, mask);
|
||||||
final Object e = lpElementNoCast(offset);
|
final Object e = lpElement(offset);
|
||||||
spElement(offset, null);
|
spElement(offset, null);
|
||||||
|
|
||||||
// Move sequence ahead by capacity, preparing it for next offer
|
// Move sequence ahead by capacity, preparing it for next offer
|
||||||
@ -410,8 +523,6 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
|
|
||||||
// whoops, inconsistent state
|
// whoops, inconsistent state
|
||||||
busySpin(POP_SPINS);
|
busySpin(POP_SPINS);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -419,97 +530,7 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
@Override
|
@Override
|
||||||
public boolean tryTransfer(Object item, long timeout, TimeUnit unit) throws InterruptedException {
|
public boolean tryTransfer(Object item, long timeout, TimeUnit unit) throws InterruptedException {
|
||||||
long nanos = unit.toNanos(timeout);
|
long nanos = unit.toNanos(timeout);
|
||||||
long lastTime = System.nanoTime();
|
return producerXfer(item, true, nanos);
|
||||||
|
|
||||||
// local load of field to avoid repeated loads after volatile reads
|
|
||||||
final long mask = this.mask;
|
|
||||||
final long[] sBuffer = this.sequenceBuffer;
|
|
||||||
|
|
||||||
long consumerIndex;
|
|
||||||
long producerIndex;
|
|
||||||
int lastType;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
consumerIndex = lvConsumerIndex();
|
|
||||||
producerIndex = lvProducerIndex();
|
|
||||||
|
|
||||||
final Object previousElement;
|
|
||||||
if (consumerIndex == producerIndex) {
|
|
||||||
lastType = TYPE_EMPTY;
|
|
||||||
previousElement = null;
|
|
||||||
} else {
|
|
||||||
previousElement = lpElementNoCast(calcElementOffset(producerIndex-1));
|
|
||||||
if (previousElement == null) {
|
|
||||||
// the last producer hasn't finished setting the object yet
|
|
||||||
busySpin(INPROGRESS_SPINS);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastType = lpType(previousElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (lastType) {
|
|
||||||
case TYPE_EMPTY:
|
|
||||||
case TYPE_PRODUCER: {
|
|
||||||
// empty or same mode = push+park onto queue
|
|
||||||
long pSeqOffset = calcSequenceOffset(producerIndex, mask);
|
|
||||||
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
|
|
||||||
final long delta = seq - producerIndex;
|
|
||||||
|
|
||||||
if (delta == 0) {
|
|
||||||
long now = System.nanoTime();
|
|
||||||
long remaining = nanos -= now - lastTime;
|
|
||||||
lastTime = now;
|
|
||||||
|
|
||||||
if (remaining > 0) {
|
|
||||||
if (remaining < SPIN_THRESHOLD) {
|
|
||||||
busySpin(PARK_UNTIMED_SPINS);
|
|
||||||
} else {
|
|
||||||
UNSAFE.park(false, 1L);
|
|
||||||
}
|
|
||||||
// make sure to continue here (so we don't spin twice)
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// whoops, inconsistent state
|
|
||||||
busySpin(PUSH_SPINS);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
case TYPE_CONSUMER: {
|
|
||||||
// complimentary mode = pop+unpark off queue
|
|
||||||
long cSeqOffset = calcSequenceOffset(consumerIndex, mask);
|
|
||||||
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
|
|
||||||
final long delta = seq - (consumerIndex + 1);
|
|
||||||
|
|
||||||
if (delta == 0) {
|
|
||||||
if (casConsumerIndex(consumerIndex, consumerIndex + 1)) {
|
|
||||||
// Successful CAS: full barrier
|
|
||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
|
||||||
final long offset = calcElementOffset(consumerIndex, mask);
|
|
||||||
final Object e = lpElementNoCast(offset);
|
|
||||||
spElement(offset, null);
|
|
||||||
|
|
||||||
// Move sequence ahead by capacity, preparing it for next offer
|
|
||||||
// (seeing this value from a consumer will lead to retry 2)
|
|
||||||
soSequence(sBuffer, cSeqOffset, consumerIndex + mask + 1); // StoreStore
|
|
||||||
|
|
||||||
soItem1(e, item);
|
|
||||||
unpark(e);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// whoops, inconsistent state
|
|
||||||
busySpin(POP_SPINS);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -537,7 +558,7 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
||||||
final long offset = calcElementOffset(consumerIndex, mask);
|
final long offset = calcElementOffset(consumerIndex, mask);
|
||||||
final Object e = lpElementNoCast(offset);
|
final Object e = lpElement(offset);
|
||||||
spElement(offset, null);
|
spElement(offset, null);
|
||||||
|
|
||||||
// Move sequence ahead by capacity, preparing it for next offer
|
// Move sequence ahead by capacity, preparing it for next offer
|
||||||
@ -560,7 +581,7 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
if (remaining < SPIN_THRESHOLD) {
|
if (remaining < SPIN_THRESHOLD) {
|
||||||
busySpin(PARK_UNTIMED_SPINS);
|
busySpin(PARK_UNTIMED_SPINS);
|
||||||
} else {
|
} else {
|
||||||
UNSAFE.park(false, 1L);
|
UnsafeAccess.UNSAFE.park(false, 1L);
|
||||||
}
|
}
|
||||||
// make sure to continue here (so we don't spin twice)
|
// make sure to continue here (so we don't spin twice)
|
||||||
continue;
|
continue;
|
||||||
@ -635,7 +656,7 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
if (consumerIndex == producerIndex) {
|
if (consumerIndex == producerIndex) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
previousElement = lpElementNoCast(calcElementOffset(producerIndex-1));
|
previousElement = lpElement(calcElementOffset(producerIndex-1));
|
||||||
if (previousElement == null) {
|
if (previousElement == null) {
|
||||||
// the last producer hasn't finished setting the object yet
|
// the last producer hasn't finished setting the object yet
|
||||||
busySpin(INPROGRESS_SPINS);
|
busySpin(INPROGRESS_SPINS);
|
||||||
@ -660,7 +681,7 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
if (consumerIndex == producerIndex) {
|
if (consumerIndex == producerIndex) {
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
previousElement = lpElementNoCast(calcElementOffset(producerIndex-1));
|
previousElement = lpElement(calcElementOffset(producerIndex-1));
|
||||||
if (previousElement == null) {
|
if (previousElement == null) {
|
||||||
// the last producer hasn't finished setting the object yet
|
// the last producer hasn't finished setting the object yet
|
||||||
busySpin(INPROGRESS_SPINS);
|
busySpin(INPROGRESS_SPINS);
|
||||||
@ -689,7 +710,7 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
if (consumerIndex == producerIndex) {
|
if (consumerIndex == producerIndex) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
previousElement = lpElementNoCast(calcElementOffset(producerIndex-1));
|
previousElement = lpElement(calcElementOffset(producerIndex-1));
|
||||||
if (previousElement == null) {
|
if (previousElement == null) {
|
||||||
// the last producer hasn't finished setting the object yet
|
// the last producer hasn't finished setting the object yet
|
||||||
busySpin(INPROGRESS_SPINS);
|
busySpin(INPROGRESS_SPINS);
|
||||||
@ -711,17 +732,21 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if the park was correctly unparked. false if there was an interruption or a timed-park expired
|
||||||
|
*/
|
||||||
@SuppressWarnings("null")
|
@SuppressWarnings("null")
|
||||||
private final void park(final Object node, final Thread myThread, final boolean timed, long nanos) {
|
private final boolean park(final Object node, final Thread myThread, final boolean timed, long nanos) {
|
||||||
long lastTime = timed ? System.nanoTime() : 0L;
|
long lastTime = System.nanoTime();
|
||||||
int spins = -1; // initialized after first item and cancel checks
|
int spins = -1; // initialized after first item and cancel checks
|
||||||
ThreadLocalRandom randomYields = null; // bound if needed
|
ThreadLocalRandom randomYields = null; // bound if needed
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (lvThread(node) == null) {
|
if (lvThread(node) == null) {
|
||||||
return;
|
return true;
|
||||||
} else if (myThread.isInterrupted() || timed && nanos <= 0) {
|
} else if (myThread.isInterrupted() || timed && nanos <= 0) {
|
||||||
return;
|
return false;
|
||||||
} else if (spins < 0) {
|
} else if (spins < 0) {
|
||||||
if (timed) {
|
if (timed) {
|
||||||
spins = PARK_TIMED_SPINS;
|
spins = PARK_TIMED_SPINS;
|
||||||
@ -743,16 +768,17 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
lastTime = now;
|
lastTime = now;
|
||||||
if (remaining > 0) {
|
if (remaining > 0) {
|
||||||
if (remaining < SPIN_THRESHOLD) {
|
if (remaining < SPIN_THRESHOLD) {
|
||||||
|
// a park is too slow for this number, so just busy spin instead
|
||||||
busySpin(PARK_UNTIMED_SPINS);
|
busySpin(PARK_UNTIMED_SPINS);
|
||||||
} else {
|
} else {
|
||||||
UNSAFE.park(false, nanos);
|
UnsafeAccess.UNSAFE.park(false, nanos);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// park can return for NO REASON (must check for thread values)
|
// park can return for NO REASON (must check for thread values)
|
||||||
UNSAFE.park(false, 0L);
|
UnsafeAccess.UNSAFE.park(false, 0L);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -760,10 +786,14 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
private final void unpark(Object node) {
|
private final void unpark(Object node) {
|
||||||
final Object thread = lpThread(node);
|
final Object thread = lpThread(node);
|
||||||
soThread(node, null);
|
soThread(node, null);
|
||||||
UNSAFE.unpark(thread);
|
UnsafeAccess.UNSAFE.unpark(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final void producerWait(final Object item, final boolean timed, final long nanos) {
|
/**
|
||||||
|
* @return true if the producer successfully transferred an item (either through waiting for a consumer that took it, or transferring
|
||||||
|
* directly to an already waiting consumer). false if the thread was interrupted, or it was timed and has expired.
|
||||||
|
*/
|
||||||
|
private final boolean producerXfer(final Object item, final boolean timed, final long nanos) {
|
||||||
// local load of field to avoid repeated loads after volatile reads
|
// local load of field to avoid repeated loads after volatile reads
|
||||||
final long mask = this.mask;
|
final long mask = this.mask;
|
||||||
final long[] sBuffer = this.sequenceBuffer;
|
final long[] sBuffer = this.sequenceBuffer;
|
||||||
@ -781,7 +811,7 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
lastType = TYPE_EMPTY;
|
lastType = TYPE_EMPTY;
|
||||||
previousElement = null;
|
previousElement = null;
|
||||||
} else {
|
} else {
|
||||||
previousElement = lpElementNoCast(calcElementOffset(producerIndex-1));
|
previousElement = lpElement(calcElementOffset(producerIndex-1));
|
||||||
if (previousElement == null) {
|
if (previousElement == null) {
|
||||||
// the last producer hasn't finished setting the object yet
|
// the last producer hasn't finished setting the object yet
|
||||||
busySpin(INPROGRESS_SPINS);
|
busySpin(INPROGRESS_SPINS);
|
||||||
@ -791,9 +821,8 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
lastType = lpType(previousElement);
|
lastType = lpType(previousElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (lastType) {
|
if (lastType != TYPE_CONSUMER) {
|
||||||
case TYPE_EMPTY:
|
// TYPE_EMPTY, TYPE_PRODUCER
|
||||||
case TYPE_PRODUCER: {
|
|
||||||
// empty or same mode = push+park onto queue
|
// empty or same mode = push+park onto queue
|
||||||
long pSeqOffset = calcSequenceOffset(producerIndex, mask);
|
long pSeqOffset = calcSequenceOffset(producerIndex, mask);
|
||||||
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
|
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
|
||||||
@ -821,16 +850,14 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
// (seeing this value from a producer will lead to retry 2)
|
// (seeing this value from a producer will lead to retry 2)
|
||||||
soSequence(sBuffer, pSeqOffset, producerIndex + 1); // StoreStore
|
soSequence(sBuffer, pSeqOffset, producerIndex + 1); // StoreStore
|
||||||
|
|
||||||
park(node, myThread, timed, nanos);
|
return park(node, myThread, timed, nanos);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// whoops, inconsistent state
|
// whoops, inconsistent state
|
||||||
busySpin(PUSH_SPINS);
|
busySpin(PUSH_SPINS);
|
||||||
continue;
|
} else {
|
||||||
}
|
// TYPE_CONSUMER
|
||||||
case TYPE_CONSUMER: {
|
|
||||||
// complimentary mode = pop+unpark off queue
|
// complimentary mode = pop+unpark off queue
|
||||||
long cSeqOffset = calcSequenceOffset(consumerIndex, mask);
|
long cSeqOffset = calcSequenceOffset(consumerIndex, mask);
|
||||||
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
|
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
|
||||||
@ -842,7 +869,7 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
||||||
final long offset = calcElementOffset(consumerIndex, mask);
|
final long offset = calcElementOffset(consumerIndex, mask);
|
||||||
final Object e = lpElementNoCast(offset);
|
final Object e = lpElement(offset);
|
||||||
spElement(offset, null);
|
spElement(offset, null);
|
||||||
|
|
||||||
// Move sequence ahead by capacity, preparing it for next offer
|
// Move sequence ahead by capacity, preparing it for next offer
|
||||||
@ -852,116 +879,12 @@ public final class MpmcTransferArrayQueue extends MpmcArrayQueueConsumerField<Ob
|
|||||||
soItem1(e, item);
|
soItem1(e, item);
|
||||||
unpark(e);
|
unpark(e);
|
||||||
|
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// whoops, inconsistent state
|
// whoops, inconsistent state
|
||||||
busySpin(POP_SPINS);
|
busySpin(POP_SPINS);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Object consumerWait(final boolean timed, final long nanos) {
|
|
||||||
// local load of field to avoid repeated loads after volatile reads
|
|
||||||
final long mask = this.mask;
|
|
||||||
final long[] sBuffer = this.sequenceBuffer;
|
|
||||||
|
|
||||||
long consumerIndex;
|
|
||||||
long producerIndex;
|
|
||||||
int lastType;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
consumerIndex = lvConsumerIndex();
|
|
||||||
producerIndex = lvProducerIndex();
|
|
||||||
|
|
||||||
final Object previousElement;
|
|
||||||
if (consumerIndex == producerIndex) {
|
|
||||||
lastType = TYPE_EMPTY;
|
|
||||||
previousElement = null;
|
|
||||||
} else {
|
|
||||||
previousElement = lpElementNoCast(calcElementOffset(producerIndex-1));
|
|
||||||
if (previousElement == null) {
|
|
||||||
// the last producer hasn't finished setting the object yet
|
|
||||||
busySpin(INPROGRESS_SPINS);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastType = lpType(previousElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (lastType) {
|
|
||||||
case TYPE_EMPTY:
|
|
||||||
case TYPE_CONSUMER: {
|
|
||||||
// empty or same mode = push+park onto queue
|
|
||||||
long pSeqOffset = calcSequenceOffset(producerIndex, mask);
|
|
||||||
final long seq = lvSequence(sBuffer, pSeqOffset); // LoadLoad
|
|
||||||
final long delta = seq - producerIndex;
|
|
||||||
|
|
||||||
if (delta == 0) {
|
|
||||||
// this is expected if we see this first time around
|
|
||||||
if (casProducerIndex(producerIndex, producerIndex + 1)) {
|
|
||||||
// Successful CAS: full barrier
|
|
||||||
|
|
||||||
final Thread myThread = Thread.currentThread();
|
|
||||||
final Object node = nodeThreadLocal.get();
|
|
||||||
|
|
||||||
spType(node, TYPE_CONSUMER);
|
|
||||||
spThread(node, myThread);
|
|
||||||
|
|
||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
|
||||||
final long offset = calcElementOffset(producerIndex, mask);
|
|
||||||
spElement(offset, node);
|
|
||||||
|
|
||||||
|
|
||||||
// increment sequence by 1, the value expected by consumer
|
|
||||||
// (seeing this value from a producer will lead to retry 2)
|
|
||||||
soSequence(sBuffer, pSeqOffset, producerIndex + 1); // StoreStore
|
|
||||||
|
|
||||||
park(node, myThread, timed, nanos);
|
|
||||||
Object item1 = lvItem1(node);
|
|
||||||
|
|
||||||
return item1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// whoops, inconsistent state
|
|
||||||
busySpin(PUSH_SPINS);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
case TYPE_PRODUCER: {
|
|
||||||
// complimentary mode = pop+unpark off queue
|
|
||||||
long cSeqOffset = calcSequenceOffset(consumerIndex, mask);
|
|
||||||
final long seq = lvSequence(sBuffer, cSeqOffset); // LoadLoad
|
|
||||||
final long delta = seq - (consumerIndex + 1);
|
|
||||||
|
|
||||||
if (delta == 0) {
|
|
||||||
if (casConsumerIndex(consumerIndex, consumerIndex + 1)) {
|
|
||||||
// Successful CAS: full barrier
|
|
||||||
|
|
||||||
// on 64bit(no compressed oops) JVM this is the same as seqOffset
|
|
||||||
final long offset = calcElementOffset(consumerIndex, mask);
|
|
||||||
final Object e = lpElementNoCast(offset);
|
|
||||||
spElement(offset, null);
|
|
||||||
|
|
||||||
// Move sequence ahead by capacity, preparing it for next offer
|
|
||||||
// (seeing this value from a consumer will lead to retry 2)
|
|
||||||
soSequence(sBuffer, cSeqOffset, consumerIndex + mask + 1); // StoreStore
|
|
||||||
|
|
||||||
final Object lvItem1 = lpItem1(e);
|
|
||||||
unpark(e);
|
|
||||||
|
|
||||||
return lvItem1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// whoops, inconsistent state
|
|
||||||
busySpin(POP_SPINS);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package dorkbox.util.messagebus.common.simpleq.jctools;
|
package dorkbox.util.messagebus.common.simpleq.jctools;
|
||||||
|
|
||||||
import static dorkbox.util.messagebus.common.simpleq.jctools.UnsafeAccess.UNSAFE;
|
import org.jctools.util.UnsafeAccess;
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.simpleq.MessageType;
|
import dorkbox.util.messagebus.common.simpleq.MessageType;
|
||||||
|
|
||||||
abstract class ColdItems {
|
abstract class ColdItems {
|
||||||
@ -29,9 +30,9 @@ public class Node extends HotItem1 {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
TYPE = UNSAFE.objectFieldOffset(Node.class.getField("type"));
|
TYPE = UnsafeAccess.UNSAFE.objectFieldOffset(Node.class.getField("type"));
|
||||||
ITEM1 = UNSAFE.objectFieldOffset(Node.class.getField("item1"));
|
ITEM1 = UnsafeAccess.UNSAFE.objectFieldOffset(Node.class.getField("item1"));
|
||||||
THREAD = UNSAFE.objectFieldOffset(Node.class.getField("thread"));
|
THREAD = UnsafeAccess.UNSAFE.objectFieldOffset(Node.class.getField("thread"));
|
||||||
|
|
||||||
// now make sure we can access UNSAFE
|
// now make sure we can access UNSAFE
|
||||||
Node node = new Node();
|
Node node = new Node();
|
||||||
@ -49,45 +50,45 @@ public class Node extends HotItem1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static final void spItem1(Object node, Object item) {
|
static final void spItem1(Object node, Object item) {
|
||||||
UNSAFE.putObject(node, ITEM1, item);
|
UnsafeAccess.UNSAFE.putObject(node, ITEM1, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final void soItem1(Object node, Object item) {
|
static final void soItem1(Object node, Object item) {
|
||||||
UNSAFE.putOrderedObject(node, ITEM1, item);
|
UnsafeAccess.UNSAFE.putOrderedObject(node, ITEM1, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final Object lpItem1(Object node) {
|
static final Object lpItem1(Object node) {
|
||||||
return UNSAFE.getObject(node, ITEM1);
|
return UnsafeAccess.UNSAFE.getObject(node, ITEM1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final Object lvItem1(Object node) {
|
static final Object lvItem1(Object node) {
|
||||||
return UNSAFE.getObjectVolatile(node, ITEM1);
|
return UnsafeAccess.UNSAFE.getObjectVolatile(node, ITEM1);
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////
|
//////////////
|
||||||
static final void spType(Object node, int type) {
|
static final void spType(Object node, int type) {
|
||||||
UNSAFE.putInt(node, TYPE, type);
|
UnsafeAccess.UNSAFE.putInt(node, TYPE, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final int lpType(Object node) {
|
static final int lpType(Object node) {
|
||||||
return UNSAFE.getInt(node, TYPE);
|
return UnsafeAccess.UNSAFE.getInt(node, TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////
|
///////////
|
||||||
static final void spThread(Object node, Object thread) {
|
static final void spThread(Object node, Object thread) {
|
||||||
UNSAFE.putObject(node, THREAD, thread);
|
UnsafeAccess.UNSAFE.putObject(node, THREAD, thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final void soThread(Object node, Object thread) {
|
static final void soThread(Object node, Object thread) {
|
||||||
UNSAFE.putOrderedObject(node, THREAD, thread);
|
UnsafeAccess.UNSAFE.putOrderedObject(node, THREAD, thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final Object lpThread(Object node) {
|
static final Object lpThread(Object node) {
|
||||||
return UNSAFE.getObject(node, THREAD);
|
return UnsafeAccess.UNSAFE.getObject(node, THREAD);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final Object lvThread(Object node) {
|
static final Object lvThread(Object node) {
|
||||||
return UNSAFE.getObjectVolatile(node, THREAD);
|
return UnsafeAccess.UNSAFE.getObjectVolatile(node, THREAD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
* https://github.com/JCTools/JCTools
|
|
||||||
*
|
|
||||||
* Licensed 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 dorkbox.util.messagebus.common.simpleq.jctools;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Power of 2 utility functions.
|
|
||||||
*/
|
|
||||||
public class Pow2 {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the next larger positive power of two value up from the given value. If value is a power of two then
|
|
||||||
* this value will be returned.
|
|
||||||
*
|
|
||||||
* @param value from which next positive power of two will be found.
|
|
||||||
* @return the next positive power of 2 or this value if it is a power of 2.
|
|
||||||
*/
|
|
||||||
public static int roundToPowerOfTwo(final int value) {
|
|
||||||
return 1 << 32 - Integer.numberOfLeadingZeros(value - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is this value a power of two.
|
|
||||||
*
|
|
||||||
* @param value to be tested to see if it is a power of two.
|
|
||||||
* @return true if the value is a power of 2 otherwise false.
|
|
||||||
*/
|
|
||||||
public static boolean isPowerOfTwo(final int value) {
|
|
||||||
return (value & value - 1) == 0;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
* https://github.com/JCTools/JCTools
|
|
||||||
*
|
|
||||||
* Licensed 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 dorkbox.util.messagebus.common.simpleq.jctools;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.concurrent.atomic.AtomicReferenceArray;
|
|
||||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
|
||||||
|
|
||||||
import sun.misc.Unsafe;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Why should we resort to using Unsafe?<br>
|
|
||||||
* <ol>
|
|
||||||
* <li>To construct class fields which allow volatile/ordered/plain access: This requirement is covered by
|
|
||||||
* {@link AtomicReferenceFieldUpdater} and similar but their performance is arguably worse than the DIY approach
|
|
||||||
* (depending on JVM version) while Unsafe intrinsification is a far lesser challenge for JIT compilers.
|
|
||||||
* <li>To construct flavors of {@link AtomicReferenceArray}.
|
|
||||||
* <li>Other use cases exist but are not present in this library yet.
|
|
||||||
* </ol>
|
|
||||||
*
|
|
||||||
* @author nitsanw
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class UnsafeAccess {
|
|
||||||
public static final boolean SUPPORTS_GET_AND_SET;
|
|
||||||
public static final Unsafe UNSAFE;
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
final Field field = Unsafe.class.getDeclaredField("theUnsafe");
|
|
||||||
field.setAccessible(true);
|
|
||||||
UNSAFE = (Unsafe) field.get(null);
|
|
||||||
} catch (Exception e) {
|
|
||||||
SUPPORTS_GET_AND_SET = false;
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
boolean getAndSetSupport = false;
|
|
||||||
try {
|
|
||||||
Unsafe.class.getMethod("getAndSetObject", Object.class, Long.TYPE,Object.class);
|
|
||||||
getAndSetSupport = true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
}
|
|
||||||
SUPPORTS_GET_AND_SET = getAndSetSupport;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012 Real Logic Ltd.
|
|
||||||
*
|
|
||||||
* Licensed 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 dorkbox.util.messagebus;
|
|
||||||
|
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
|
||||||
|
|
||||||
import org.openjdk.jol.info.ClassLayout;
|
|
||||||
import org.openjdk.jol.util.VMSupport;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.simpleq.jctools.Node;
|
|
||||||
|
|
||||||
public class LinkTransferQueuePerfTest {
|
|
||||||
// 15 == 32 * 1024
|
|
||||||
public static final int REPETITIONS = Integer.getInteger("reps", 50) * 1000 * 100;
|
|
||||||
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
|
||||||
|
|
||||||
public static final int QUEUE_CAPACITY = 1 << Integer.getInteger("pow2.capacity", 17);
|
|
||||||
|
|
||||||
public static void main(final String[] args) throws Exception {
|
|
||||||
System.out.println(VMSupport.vmDetails());
|
|
||||||
System.out.println(ClassLayout.parseClass(Node.class).toPrintable());
|
|
||||||
|
|
||||||
|
|
||||||
System.out.println("capacity:" + QUEUE_CAPACITY + " reps:" + REPETITIONS);
|
|
||||||
|
|
||||||
final LinkedTransferQueue<Integer> queue = new LinkedTransferQueue<Integer>();
|
|
||||||
|
|
||||||
final long[] results = new long[20];
|
|
||||||
for (int i = 0; i < 20; i++) {
|
|
||||||
System.gc();
|
|
||||||
results[i] = performanceRun(i, queue);
|
|
||||||
}
|
|
||||||
// only average last 10 results for summary
|
|
||||||
long sum = 0;
|
|
||||||
for (int i = 10; i < 20; i++) {
|
|
||||||
sum += results[i];
|
|
||||||
}
|
|
||||||
System.out.format("summary,QueuePerfTest,%s,%d\n", queue.getClass().getSimpleName(), sum / 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static long performanceRun(int runNumber, LinkedTransferQueue<Integer> queue) throws Exception {
|
|
||||||
Producer p = new Producer(queue);
|
|
||||||
Thread thread = new Thread(p);
|
|
||||||
thread.start(); // producer will timestamp start
|
|
||||||
|
|
||||||
LinkedTransferQueue<Integer> consumer = queue;
|
|
||||||
Object result;
|
|
||||||
int i = REPETITIONS;
|
|
||||||
int queueEmpty = 0;
|
|
||||||
do {
|
|
||||||
result = consumer.take();
|
|
||||||
} while (0 != --i);
|
|
||||||
long end = System.nanoTime();
|
|
||||||
|
|
||||||
thread.join();
|
|
||||||
long duration = end - p.start;
|
|
||||||
long ops = REPETITIONS * 1000L * 1000L * 1000L / duration;
|
|
||||||
String qName = queue.getClass().getSimpleName();
|
|
||||||
System.out.format("%d - ops/sec=%,d - %s result=%d failed.poll=%d failed.offer=%d\n", runNumber, ops,
|
|
||||||
qName, result, queueEmpty, p.queueFull);
|
|
||||||
return ops;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Producer implements Runnable {
|
|
||||||
private final LinkedTransferQueue<Integer> queue;
|
|
||||||
int queueFull = 0;
|
|
||||||
long start;
|
|
||||||
|
|
||||||
public Producer(LinkedTransferQueue<Integer> queue) {
|
|
||||||
this.queue = queue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
LinkedTransferQueue<Integer> producer = this.queue;
|
|
||||||
int i = REPETITIONS;
|
|
||||||
int f = 0;
|
|
||||||
long s = System.nanoTime();
|
|
||||||
|
|
||||||
try {
|
|
||||||
do {
|
|
||||||
producer.transfer(TEST_VALUE);
|
|
||||||
} while (0 != --i);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
// TODO Auto-generated catch block
|
|
||||||
e.printStackTrace();
|
|
||||||
// log.error(e);
|
|
||||||
}
|
|
||||||
this.queueFull = f;
|
|
||||||
this.start = s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 Real Logic Ltd.
|
||||||
|
*
|
||||||
|
* Licensed 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 dorkbox.util.messagebus;
|
||||||
|
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
|
public class PerfTest_LinkedBlockingQueue {
|
||||||
|
// 15 == 32 * 1024
|
||||||
|
public static final int REPETITIONS = Integer.getInteger("reps", 50) * 1000 * 100;
|
||||||
|
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
||||||
|
|
||||||
|
public static final int QUEUE_CAPACITY = 1 << Integer.getInteger("pow2.capacity", 17);
|
||||||
|
|
||||||
|
private static final int concurrency = 2;
|
||||||
|
|
||||||
|
public static void main(final String[] args) throws Exception {
|
||||||
|
System.out.println("capacity:" + QUEUE_CAPACITY + " reps:" + REPETITIONS + " Concurrency " + concurrency);
|
||||||
|
final LinkedBlockingQueue queue = new LinkedBlockingQueue(1 << 17);
|
||||||
|
|
||||||
|
final long[] results = new long[20];
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
System.gc();
|
||||||
|
results[i] = performanceRun(i, queue);
|
||||||
|
}
|
||||||
|
// only average last 10 results for summary
|
||||||
|
long sum = 0;
|
||||||
|
for (int i = 10; i < 20; i++) {
|
||||||
|
sum += results[i];
|
||||||
|
}
|
||||||
|
System.out.format("summary,QueuePerfTest,%s %,d\n", queue.getClass().getSimpleName(), sum / 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long performanceRun(int runNumber, LinkedBlockingQueue queue) throws Exception {
|
||||||
|
|
||||||
|
Producer[] producers = new Producer[concurrency];
|
||||||
|
Consumer[] consumers = new Consumer[concurrency];
|
||||||
|
Thread[] threads = new Thread[concurrency*2];
|
||||||
|
|
||||||
|
for (int i=0;i<concurrency;i++) {
|
||||||
|
producers[i] = new Producer(queue);
|
||||||
|
consumers[i] = new Consumer(queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j=0,i=0;i<concurrency;i++,j+=2) {
|
||||||
|
threads[j] = new Thread(producers[i], "Producer " + i);
|
||||||
|
threads[j+1] = new Thread(consumers[i], "Consumer " + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i=0;i<concurrency*2;i+=2) {
|
||||||
|
threads[i].start();
|
||||||
|
threads[i+1].start();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i=0;i<concurrency*2;i+=2) {
|
||||||
|
threads[i].join();
|
||||||
|
threads[i+1].join();
|
||||||
|
}
|
||||||
|
|
||||||
|
long start = Long.MAX_VALUE;
|
||||||
|
long end = -1;
|
||||||
|
|
||||||
|
for (int i=0;i<concurrency;i++) {
|
||||||
|
if (producers[i].start < start) {
|
||||||
|
start = producers[i].start;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (consumers[i].end > end) {
|
||||||
|
end = consumers[i].end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
long duration = end - start;
|
||||||
|
long ops = REPETITIONS * 1000L * 1000L * 1000L / duration;
|
||||||
|
String qName = queue.getClass().getSimpleName();
|
||||||
|
|
||||||
|
System.out.format("%d - ops/sec=%,d - %s\n", runNumber, ops, qName);
|
||||||
|
return ops;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Producer implements Runnable {
|
||||||
|
private final LinkedBlockingQueue queue;
|
||||||
|
volatile long start;
|
||||||
|
|
||||||
|
public Producer(LinkedBlockingQueue queue) {
|
||||||
|
this.queue = queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
LinkedBlockingQueue producer = this.queue;
|
||||||
|
int i = REPETITIONS;
|
||||||
|
this.start = System.nanoTime();
|
||||||
|
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
producer.put(TEST_VALUE);
|
||||||
|
} while (0 != --i);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
// log.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Consumer implements Runnable {
|
||||||
|
private final LinkedBlockingQueue queue;
|
||||||
|
Object result;
|
||||||
|
volatile long end;
|
||||||
|
|
||||||
|
public Consumer(LinkedBlockingQueue queue) {
|
||||||
|
this.queue = queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
LinkedBlockingQueue consumer = this.queue;
|
||||||
|
Object result = null;
|
||||||
|
int i = REPETITIONS;
|
||||||
|
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
result = consumer.take();
|
||||||
|
} while (0 != --i);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
// log.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.result = result;
|
||||||
|
this.end = System.nanoTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 Real Logic Ltd.
|
||||||
|
*
|
||||||
|
* Licensed 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 dorkbox.util.messagebus;
|
||||||
|
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
|
public class PerfTest_LinkedBlockingQueue_NonBlock {
|
||||||
|
// 15 == 32 * 1024
|
||||||
|
public static final int REPETITIONS = Integer.getInteger("reps", 50) * 1000 * 100;
|
||||||
|
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
||||||
|
|
||||||
|
public static final int QUEUE_CAPACITY = 1 << Integer.getInteger("pow2.capacity", 17);
|
||||||
|
|
||||||
|
private static final int concurrency = 2;
|
||||||
|
|
||||||
|
public static void main(final String[] args) throws Exception {
|
||||||
|
System.out.println("capacity:" + QUEUE_CAPACITY + " reps:" + REPETITIONS + " Concurrency " + concurrency);
|
||||||
|
final LinkedBlockingQueue queue = new LinkedBlockingQueue(1 << 17);
|
||||||
|
|
||||||
|
final long[] results = new long[20];
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
System.gc();
|
||||||
|
results[i] = performanceRun(i, queue);
|
||||||
|
}
|
||||||
|
// only average last 10 results for summary
|
||||||
|
long sum = 0;
|
||||||
|
for (int i = 10; i < 20; i++) {
|
||||||
|
sum += results[i];
|
||||||
|
}
|
||||||
|
System.out.format("summary,QueuePerfTest,%s %,d\n", queue.getClass().getSimpleName(), sum / 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long performanceRun(int runNumber, LinkedBlockingQueue queue) throws Exception {
|
||||||
|
|
||||||
|
Producer[] producers = new Producer[concurrency];
|
||||||
|
Consumer[] consumers = new Consumer[concurrency];
|
||||||
|
Thread[] threads = new Thread[concurrency*2];
|
||||||
|
|
||||||
|
for (int i=0;i<concurrency;i++) {
|
||||||
|
producers[i] = new Producer(queue);
|
||||||
|
consumers[i] = new Consumer(queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j=0,i=0;i<concurrency;i++,j+=2) {
|
||||||
|
threads[j] = new Thread(producers[i], "Producer " + i);
|
||||||
|
threads[j+1] = new Thread(consumers[i], "Consumer " + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i=0;i<concurrency*2;i+=2) {
|
||||||
|
threads[i].start();
|
||||||
|
threads[i+1].start();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i=0;i<concurrency*2;i+=2) {
|
||||||
|
threads[i].join();
|
||||||
|
threads[i+1].join();
|
||||||
|
}
|
||||||
|
|
||||||
|
long start = Long.MAX_VALUE;
|
||||||
|
long end = -1;
|
||||||
|
|
||||||
|
for (int i=0;i<concurrency;i++) {
|
||||||
|
if (producers[i].start < start) {
|
||||||
|
start = producers[i].start;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (consumers[i].end > end) {
|
||||||
|
end = consumers[i].end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
long duration = end - start;
|
||||||
|
long ops = REPETITIONS * 1000L * 1000L * 1000L / duration;
|
||||||
|
String qName = queue.getClass().getSimpleName();
|
||||||
|
|
||||||
|
System.out.format("%d - ops/sec=%,d - %s\n", runNumber, ops, qName);
|
||||||
|
return ops;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Producer implements Runnable {
|
||||||
|
private final LinkedBlockingQueue queue;
|
||||||
|
volatile long start;
|
||||||
|
|
||||||
|
public Producer(LinkedBlockingQueue queue) {
|
||||||
|
this.queue = queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
LinkedBlockingQueue producer = this.queue;
|
||||||
|
int i = REPETITIONS;
|
||||||
|
this.start = System.nanoTime();
|
||||||
|
|
||||||
|
do {
|
||||||
|
while (!producer.offer(TEST_VALUE)) {
|
||||||
|
Thread.yield();
|
||||||
|
}
|
||||||
|
} while (0 != --i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Consumer implements Runnable {
|
||||||
|
private final LinkedBlockingQueue queue;
|
||||||
|
Object result;
|
||||||
|
volatile long end;
|
||||||
|
|
||||||
|
public Consumer(LinkedBlockingQueue queue) {
|
||||||
|
this.queue = queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
LinkedBlockingQueue consumer = this.queue;
|
||||||
|
Object result = null;
|
||||||
|
int i = REPETITIONS;
|
||||||
|
|
||||||
|
do {
|
||||||
|
while (null == (result = consumer.poll())) {
|
||||||
|
Thread.yield();
|
||||||
|
}
|
||||||
|
} while (0 != --i);
|
||||||
|
|
||||||
|
this.result = result;
|
||||||
|
this.end = System.nanoTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,24 +14,16 @@ package dorkbox.util.messagebus;
|
|||||||
|
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
|
|
||||||
import org.openjdk.jol.info.ClassLayout;
|
public class PerfTest_LinkedTransferQueue {
|
||||||
import org.openjdk.jol.util.VMSupport;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.simpleq.jctools.Node;
|
|
||||||
|
|
||||||
public class LinkTransferQueueConcurrentPerfTest {
|
|
||||||
// 15 == 32 * 1024
|
// 15 == 32 * 1024
|
||||||
public static final int REPETITIONS = Integer.getInteger("reps", 50) * 1000 * 100;
|
public static final int REPETITIONS = Integer.getInteger("reps", 50) * 1000 * 100;
|
||||||
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
||||||
|
|
||||||
public static final int QUEUE_CAPACITY = 1 << Integer.getInteger("pow2.capacity", 17);
|
public static final int QUEUE_CAPACITY = 1 << Integer.getInteger("pow2.capacity", 17);
|
||||||
|
|
||||||
private static final int concurrency = 2;
|
private static final int concurrency = 1;
|
||||||
|
|
||||||
public static void main(final String[] args) throws Exception {
|
public static void main(final String[] args) throws Exception {
|
||||||
System.out.println(VMSupport.vmDetails());
|
|
||||||
System.out.println(ClassLayout.parseClass(Node.class).toPrintable());
|
|
||||||
|
|
||||||
System.out.println("capacity:" + QUEUE_CAPACITY + " reps:" + REPETITIONS + " Concurrency " + concurrency);
|
System.out.println("capacity:" + QUEUE_CAPACITY + " reps:" + REPETITIONS + " Concurrency " + concurrency);
|
||||||
final LinkedTransferQueue queue = new LinkedTransferQueue();
|
final LinkedTransferQueue queue = new LinkedTransferQueue();
|
||||||
|
|
@ -14,12 +14,7 @@ package dorkbox.util.messagebus;
|
|||||||
|
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
|
|
||||||
import org.openjdk.jol.info.ClassLayout;
|
public class PerfTest_LinkedTransferQueue_NonBlock {
|
||||||
import org.openjdk.jol.util.VMSupport;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.simpleq.jctools.Node;
|
|
||||||
|
|
||||||
public class LinkTransferQueueConcurrentNonBlockPerfTest {
|
|
||||||
// 15 == 32 * 1024
|
// 15 == 32 * 1024
|
||||||
public static final int REPETITIONS = Integer.getInteger("reps", 50) * 1000 * 100;
|
public static final int REPETITIONS = Integer.getInteger("reps", 50) * 1000 * 100;
|
||||||
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
||||||
@ -29,9 +24,6 @@ public class LinkTransferQueueConcurrentNonBlockPerfTest {
|
|||||||
private static final int concurrency = 4;
|
private static final int concurrency = 4;
|
||||||
|
|
||||||
public static void main(final String[] args) throws Exception {
|
public static void main(final String[] args) throws Exception {
|
||||||
System.out.println(VMSupport.vmDetails());
|
|
||||||
System.out.println(ClassLayout.parseClass(Node.class).toPrintable());
|
|
||||||
|
|
||||||
System.out.println("capacity:" + QUEUE_CAPACITY + " reps:" + REPETITIONS + " Concurrency " + concurrency);
|
System.out.println("capacity:" + QUEUE_CAPACITY + " reps:" + REPETITIONS + " Concurrency " + concurrency);
|
||||||
final LinkedTransferQueue queue = new LinkedTransferQueue();
|
final LinkedTransferQueue queue = new LinkedTransferQueue();
|
||||||
|
|
@ -15,12 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.util.messagebus;
|
package dorkbox.util.messagebus;
|
||||||
|
|
||||||
import org.openjdk.jol.info.ClassLayout;
|
import org.jctools.queues.MpmcArrayQueue;
|
||||||
import org.openjdk.jol.util.VMSupport;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.simpleq.jctools.MpmcArrayQueue;
|
|
||||||
|
|
||||||
public class MpmcQueueBaselinePerfTest {
|
public class PerfTest_MpmcArrayQueue_Baseline {
|
||||||
|
static {
|
||||||
|
System.setProperty("sparse.shift", "2");
|
||||||
|
}
|
||||||
|
|
||||||
// 15 == 32 * 1024
|
// 15 == 32 * 1024
|
||||||
public static final int REPETITIONS = Integer.getInteger("reps", 50) * 1000 * 1000;
|
public static final int REPETITIONS = Integer.getInteger("reps", 50) * 1000 * 1000;
|
||||||
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
||||||
@ -28,11 +30,8 @@ public class MpmcQueueBaselinePerfTest {
|
|||||||
public static final int QUEUE_CAPACITY = 1 << Integer.getInteger("pow2.capacity", 17);
|
public static final int QUEUE_CAPACITY = 1 << Integer.getInteger("pow2.capacity", 17);
|
||||||
|
|
||||||
public static void main(final String[] args) throws Exception {
|
public static void main(final String[] args) throws Exception {
|
||||||
System.out.println(VMSupport.vmDetails());
|
|
||||||
System.out.println(ClassLayout.parseClass(Integer.class).toPrintable());
|
|
||||||
|
|
||||||
System.out.println("capacity:" + QUEUE_CAPACITY + " reps:" + REPETITIONS);
|
System.out.println("capacity:" + QUEUE_CAPACITY + " reps:" + REPETITIONS);
|
||||||
final MpmcArrayQueue queue = new MpmcArrayQueue(QUEUE_CAPACITY);
|
final MpmcArrayQueue<Integer> queue = new MpmcArrayQueue<Integer>(QUEUE_CAPACITY);
|
||||||
|
|
||||||
final long[] results = new long[20];
|
final long[] results = new long[20];
|
||||||
for (int i = 0; i < 20; i++) {
|
for (int i = 0; i < 20; i++) {
|
||||||
@ -47,13 +46,13 @@ public class MpmcQueueBaselinePerfTest {
|
|||||||
System.out.format("summary,QueuePerfTest,%s,%d\n", queue.getClass().getSimpleName(), sum / 10);
|
System.out.format("summary,QueuePerfTest,%s,%d\n", queue.getClass().getSimpleName(), sum / 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long performanceRun(int runNumber, MpmcArrayQueue queue) throws Exception {
|
private static long performanceRun(int runNumber, MpmcArrayQueue<Integer> queue) throws Exception {
|
||||||
Producer p = new Producer(queue);
|
Producer p = new Producer(queue);
|
||||||
Thread thread = new Thread(p);
|
Thread thread = new Thread(p);
|
||||||
thread.start(); // producer will timestamp start
|
thread.start(); // producer will timestamp start
|
||||||
|
|
||||||
MpmcArrayQueue consumer = queue;
|
MpmcArrayQueue<Integer> consumer = queue;
|
||||||
Object result;
|
Integer result;
|
||||||
int i = REPETITIONS;
|
int i = REPETITIONS;
|
||||||
int queueEmpty = 0;
|
int queueEmpty = 0;
|
||||||
do {
|
do {
|
||||||
@ -74,17 +73,17 @@ public class MpmcQueueBaselinePerfTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Producer implements Runnable {
|
public static class Producer implements Runnable {
|
||||||
private final MpmcArrayQueue queue;
|
private final MpmcArrayQueue<Integer> queue;
|
||||||
int queueFull = 0;
|
int queueFull = 0;
|
||||||
long start;
|
long start;
|
||||||
|
|
||||||
public Producer(MpmcArrayQueue queue) {
|
public Producer(MpmcArrayQueue<Integer> queue) {
|
||||||
this.queue = queue;
|
this.queue = queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
MpmcArrayQueue producer = this.queue;
|
MpmcArrayQueue<Integer> producer = this.queue;
|
||||||
int i = REPETITIONS;
|
int i = REPETITIONS;
|
||||||
int f = 0;
|
int f = 0;
|
||||||
long s = System.nanoTime();
|
long s = System.nanoTime();
|
@ -15,13 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.util.messagebus;
|
package dorkbox.util.messagebus;
|
||||||
|
|
||||||
import org.openjdk.jol.info.ClassLayout;
|
import org.jctools.queues.MpmcArrayQueue;
|
||||||
import org.openjdk.jol.util.VMSupport;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.simpleq.jctools.MpmcArrayQueue;
|
|
||||||
import dorkbox.util.messagebus.common.simpleq.jctools.Node;
|
import dorkbox.util.messagebus.common.simpleq.jctools.Node;
|
||||||
|
|
||||||
public class MpmcQueueBaselineNodePerfTest {
|
public class PerfTest_MpmcArrayQueue_Baseline_Node {
|
||||||
// 15 == 32 * 1024
|
// 15 == 32 * 1024
|
||||||
public static final int REPETITIONS = Integer.getInteger("reps", 50) * 1000 * 1000;
|
public static final int REPETITIONS = Integer.getInteger("reps", 50) * 1000 * 1000;
|
||||||
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
||||||
@ -29,9 +27,6 @@ public class MpmcQueueBaselineNodePerfTest {
|
|||||||
public static final int QUEUE_CAPACITY = 1 << Integer.getInteger("pow2.capacity", 17);
|
public static final int QUEUE_CAPACITY = 1 << Integer.getInteger("pow2.capacity", 17);
|
||||||
|
|
||||||
public static void main(final String[] args) throws Exception {
|
public static void main(final String[] args) throws Exception {
|
||||||
System.out.println(VMSupport.vmDetails());
|
|
||||||
System.out.println(ClassLayout.parseClass(Node.class).toPrintable());
|
|
||||||
|
|
||||||
System.out.println("capacity:" + QUEUE_CAPACITY + " reps:" + REPETITIONS);
|
System.out.println("capacity:" + QUEUE_CAPACITY + " reps:" + REPETITIONS);
|
||||||
final MpmcArrayQueue<Node> queue = new MpmcArrayQueue<Node>(QUEUE_CAPACITY);
|
final MpmcArrayQueue<Node> queue = new MpmcArrayQueue<Node>(QUEUE_CAPACITY);
|
||||||
|
|
@ -15,25 +15,18 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.util.messagebus;
|
package dorkbox.util.messagebus;
|
||||||
|
|
||||||
import org.openjdk.jol.info.ClassLayout;
|
import org.jctools.queues.MpmcArrayQueue;
|
||||||
import org.openjdk.jol.util.VMSupport;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.simpleq.jctools.MpmcArrayQueue;
|
public class PerfTest_MpmcArrayQueue_Concurrent {
|
||||||
import dorkbox.util.messagebus.common.simpleq.jctools.Node;
|
|
||||||
|
|
||||||
public class MpmcQueueConcurrentPerfTest {
|
|
||||||
// 15 == 32 * 1024
|
// 15 == 32 * 1024
|
||||||
public static final int REPETITIONS = Integer.getInteger("reps", 50) * 1000 * 100;
|
public static final int REPETITIONS = Integer.getInteger("reps", 50) * 1000 * 1000;
|
||||||
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
||||||
|
|
||||||
public static final int QUEUE_CAPACITY = 1 << Integer.getInteger("pow2.capacity", 17);
|
public static final int QUEUE_CAPACITY = 1 << Integer.getInteger("pow2.capacity", 17);
|
||||||
|
|
||||||
private static final int concurrency = 2;
|
private static final int concurrency = 1;
|
||||||
|
|
||||||
public static void main(final String[] args) throws Exception {
|
public static void main(final String[] args) throws Exception {
|
||||||
System.out.println(VMSupport.vmDetails());
|
|
||||||
System.out.println(ClassLayout.parseClass(Node.class).toPrintable());
|
|
||||||
|
|
||||||
System.out.println("capacity:" + QUEUE_CAPACITY + " reps:" + REPETITIONS + " Concurrency " + concurrency);
|
System.out.println("capacity:" + QUEUE_CAPACITY + " reps:" + REPETITIONS + " Concurrency " + concurrency);
|
||||||
final MpmcArrayQueue queue = new MpmcArrayQueue(1 << 17);
|
final MpmcArrayQueue queue = new MpmcArrayQueue(1 << 17);
|
||||||
|
|
||||||
@ -113,7 +106,9 @@ public class MpmcQueueConcurrentPerfTest {
|
|||||||
this.start = System.nanoTime();
|
this.start = System.nanoTime();
|
||||||
|
|
||||||
do {
|
do {
|
||||||
producer.offer(TEST_VALUE);
|
while (!producer.offer(TEST_VALUE)) {
|
||||||
|
Thread.yield();
|
||||||
|
}
|
||||||
} while (0 != --i);
|
} while (0 != --i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,7 +129,9 @@ public class MpmcQueueConcurrentPerfTest {
|
|||||||
int i = REPETITIONS;
|
int i = REPETITIONS;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
result = consumer.poll();
|
while (null == (result = consumer.poll())) {
|
||||||
|
Thread.yield();
|
||||||
|
}
|
||||||
} while (0 != --i);
|
} while (0 != --i);
|
||||||
|
|
||||||
this.result = result;
|
this.result = result;
|
@ -6,15 +6,16 @@ import org.openjdk.jol.util.VMSupport;
|
|||||||
import dorkbox.util.messagebus.common.simpleq.jctools.MpmcTransferArrayQueue;
|
import dorkbox.util.messagebus.common.simpleq.jctools.MpmcTransferArrayQueue;
|
||||||
import dorkbox.util.messagebus.common.simpleq.jctools.Node;
|
import dorkbox.util.messagebus.common.simpleq.jctools.Node;
|
||||||
|
|
||||||
public class MpmcTransferPerfTest {
|
public class PerfTest_MpmcTransferArrayQueue {
|
||||||
public static final int REPETITIONS = 50 * 1000 * 100;
|
public static final int REPETITIONS = 50 * 1000 * 100;
|
||||||
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
||||||
|
|
||||||
public static final int QUEUE_CAPACITY = 1 << 17;
|
public static final int QUEUE_CAPACITY = 1 << 17;
|
||||||
|
|
||||||
private static final int concurrency = 2;
|
private static final int concurrency = 4;
|
||||||
|
|
||||||
public static void main(final String[] args) throws Exception {
|
public static void main(final String[] args) throws Exception {
|
||||||
|
|
||||||
System.out.println(VMSupport.vmDetails());
|
System.out.println(VMSupport.vmDetails());
|
||||||
System.out.println(ClassLayout.parseClass(Node.class).toPrintable());
|
System.out.println(ClassLayout.parseClass(Node.class).toPrintable());
|
||||||
|
|
@ -1,25 +1,16 @@
|
|||||||
package dorkbox.util.messagebus;
|
package dorkbox.util.messagebus;
|
||||||
|
|
||||||
import org.openjdk.jol.info.ClassLayout;
|
|
||||||
import org.openjdk.jol.util.VMSupport;
|
|
||||||
|
|
||||||
import dorkbox.util.messagebus.common.simpleq.jctools.MpmcTransferArrayQueue;
|
import dorkbox.util.messagebus.common.simpleq.jctools.MpmcTransferArrayQueue;
|
||||||
import dorkbox.util.messagebus.common.simpleq.jctools.Node;
|
|
||||||
|
|
||||||
public class MpmcNonBlockPerfTest {
|
public class PerfTest_MpmcTransferArrayQueue_NonBlock {
|
||||||
public static final int REPETITIONS = 50 * 1000 * 100;
|
public static final int REPETITIONS = 50 * 1000 * 1000;
|
||||||
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
public static final Integer TEST_VALUE = Integer.valueOf(777);
|
||||||
|
|
||||||
public static final int QUEUE_CAPACITY = 1 << 17;
|
public static final int QUEUE_CAPACITY = 1 << 17;
|
||||||
|
|
||||||
private static final int concurrency = 4;
|
private static final int concurrency = 1;
|
||||||
|
|
||||||
public static void main(final String[] args) throws Exception {
|
public static void main(final String[] args) throws Exception {
|
||||||
System.out.println(VMSupport.vmDetails());
|
|
||||||
System.out.println(ClassLayout.parseClass(Node.class).toPrintable());
|
|
||||||
|
|
||||||
System.out.println("capacity:" + QUEUE_CAPACITY + " reps:" + REPETITIONS + " Concurrency " + concurrency);
|
|
||||||
|
|
||||||
final int warmupRuns = 2;
|
final int warmupRuns = 2;
|
||||||
final int runs = 5;
|
final int runs = 5;
|
||||||
|
|
||||||
@ -129,13 +120,11 @@ public class MpmcNonBlockPerfTest {
|
|||||||
int i = REPETITIONS;
|
int i = REPETITIONS;
|
||||||
this.start = System.nanoTime();
|
this.start = System.nanoTime();
|
||||||
|
|
||||||
try {
|
|
||||||
do {
|
do {
|
||||||
producer.put(TEST_VALUE);
|
while (!producer.offer(TEST_VALUE)) {
|
||||||
} while (0 != --i);
|
Thread.yield();
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
} while (0 != --i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +144,7 @@ public class MpmcNonBlockPerfTest {
|
|||||||
int i = REPETITIONS;
|
int i = REPETITIONS;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
while((result = consumer.poll()) == null) {
|
while (null == (result = consumer.poll())) {
|
||||||
Thread.yield();
|
Thread.yield();
|
||||||
}
|
}
|
||||||
} while (0 != --i);
|
} while (0 != --i);
|
Loading…
Reference in New Issue
Block a user