/**
* Copyright 2013, Landz and its contributors. All rights reserved.
*
* 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 z.basic;
import com.lmax.disruptor.*;
import com.lmax.disruptor.util.DaemonThreadFactory;
import org.HdrHistogram.Histogram;
import org.junit.Test;
import java.io.PrintStream;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedTransferQueue;
import static com.lmax.disruptor.RingBuffer.createSingleProducer;
import static java.lang.Math.max;
import static org.junit.Assert.assertTrue;
/**
* <pre>
*
* Pipeline a series of stages from a publisher to ultimate event processor.
* Each event processor depends on the output of the event processor.
*
* +----+ +-----+ +-----+ +-----+
* | P1 |--->| EP1 |--->| EP2 |--->| EP3 |
* +----+ +-----+ +-----+ +-----+
*
*
* Queue Based:
* ============
*
* put take put take put take
* +----+ +====+ +-----+ +====+ +-----+ +====+ +-----+
* | P1 |--->| Q1 |<---| EP1 |--->| Q2 |<---| EP2 |--->| Q3 |<---| EP3 |
* +----+ +====+ +-----+ +====+ +-----+ +====+ +-----+
*
* P1 - Publisher 1
* Q1 - Queue 1
* EP1 - EventProcessor 1
* Q2 - Queue 2
* EP2 - EventProcessor 2
* Q3 - Queue 3
* EP3 - EventProcessor 3
*
*
* Disruptor:
* ==========
* track to prevent wrap
* +----------------------------------------------------------------+
* | |
* | v
* +----+ +====+ +=====+ +-----+ +=====+ +-----+ +=====+ +-----+
* | P1 |--->| RB | | SB1 |<---| EP1 |<---| SB2 |<---| EP2 |<---| SB3 |<---| EP3 |
* +----+ +====+ +=====+ +-----+ +=====+ +-----+ +=====+ +-----+
* claim ^ get | waitFor | waitFor | waitFor
* | | | |
* +---------+---------------------+---------------------+
* </pre>
*
* P1 - Publisher 1
* RB - Hyperloop
* SB1 - SequenceBarrier 1
* EP1 - EventProcessor 1
* SB2 - SequenceBarrier 2
* EP2 - EventProcessor 2
* SB3 - SequenceBarrier 3
* EP3 - EventProcessor 3
*
* </pre>
*
* Note: <b>This test is only useful on a system using an invariant TSC in user space from the System.nanoTime() call.</b>
*
* Note: this test is a modified version of Disruptor's ThrottledOnePublisherToThreeProcessorPipelineLatencyTest
*/
public final class ThrottledOnePublisherToThreeProcessorPipelineLatencyTestV2
{
private static final int NUM_EVENT_PROCESSORS = 3;
private static final int BUFFER_SIZE = 1024;
private static final long ITERATIONS = 10_000_000L;
private static final long PAUSE_NANOS = 1_000L;
private final ExecutorService executor = Executors.newFixedThreadPool(NUM_EVENT_PROCESSORS, DaemonThreadFactory.INSTANCE);
private final Histogram histogram = new Histogram(10_000_000_000L, 4);
///////////////////////////////////////////////////////////////////////////////////////////////
private final LinkedTransferQueue<Long> stepOneQueue = new LinkedTransferQueue<>();//LinkedBlockingQueue<Long>(BUFFER_SIZE);
private final LinkedTransferQueue<Long> stepTwoQueue = new LinkedTransferQueue<>();//LinkedBlockingQueue<Long>(BUFFER_SIZE);
private final LinkedTransferQueue<Long> stepThreeQueue = new LinkedTransferQueue<>();//LinkedBlockingQueue<Long>(BUFFER_SIZE);
private final LatencyQueueProcessor stepOneQueueProcessor =
new LatencyQueueProcessor(stepOneQueue, stepTwoQueue, ITERATIONS);
private final LatencyQueueProcessor stepTwoQueueProcessor =
new LatencyQueueProcessor(stepTwoQueue, stepThreeQueue, ITERATIONS);
// private final LatencyStepOneQueueProcessor stepThreeQueueProcessor =
// new LatencyStepOneQueueProcessor(FunctionStep.THREE, stepThreeQueue, null, ITERATIONS - 1);
///////////////////////////////////////////////////////////////////////////////////////////////
private final RingBuffer<ValueEvent> ringBuffer =
createSingleProducer(ValueEvent.EVENT_FACTORY, BUFFER_SIZE, new YieldingWaitStrategy());
private final SequenceBarrier stepOneSequenceBarrier = ringBuffer.newBarrier();
private final LatencyStepEventHandler stepOneFunctionHandler = new LatencyStepEventHandler(FunctionStep.ONE);
private final BatchEventProcessor<ValueEvent> stepOneBatchProcessor =
new BatchEventProcessor<ValueEvent>(ringBuffer, stepOneSequenceBarrier, stepOneFunctionHandler);
private final SequenceBarrier stepTwoSequenceBarrier = ringBuffer.newBarrier(stepOneBatchProcessor.getSequence());
private final LatencyStepEventHandler stepTwoFunctionHandler = new LatencyStepEventHandler(FunctionStep.TWO);
private final BatchEventProcessor<ValueEvent> stepTwoBatchProcessor =
new BatchEventProcessor<ValueEvent>(ringBuffer, stepTwoSequenceBarrier, stepTwoFunctionHandler);
private final SequenceBarrier stepThreeSequenceBarrier = ringBuffer.newBarrier(stepTwoBatchProcessor.getSequence());
private final LatencyStepEventHandler stepThreeFunctionHandler = new LatencyStepEventHandler(FunctionStep.THREE);
private final BatchEventProcessor<ValueEvent> stepThreeBatchProcessor =
new BatchEventProcessor<ValueEvent>(ringBuffer, stepThreeSequenceBarrier, stepThreeFunctionHandler);
{
ringBuffer.addGatingSequences(stepThreeBatchProcessor.getSequence());
}
///////////////////////////////////////////////////////////////////////////////////////////////
@Test
public void shouldCompareDisruptorVsQueues() throws Exception
{
final int runs = 2;
double[] queueMeanLatency = new double[runs];
double[] disruptorMeanLatency = new double[runs];
///////////////////////////////////////////////////////////////////
if ("true".equalsIgnoreCase(System.getProperty("com.lmax.runQueueTests", "true")))
{
for (int i = 0; i < runs; i++)
{
System.gc();
histogram.reset();
runQueuePass();
assertTrue(histogram.getHistogramData().getTotalCount() >= ITERATIONS);
queueMeanLatency[i] = histogram.getHistogramData().getMean();
System.out.format("%s run %d TransferQueue %s\n", getClass().getSimpleName(), Long.valueOf(i), histogram);
dumpHistogram(histogram, System.out);
}
}
else
{
for (int i = 0; i < runs; i++)
{
queueMeanLatency[i] = Double.MAX_VALUE;
}
}
/////////////////////////////////////////////////////////////////
for (int i = 0; i < runs; i++)
{
System.gc();
histogram.reset();
runDisruptorPass();
assertTrue(histogram.getHistogramData().getTotalCount() >= ITERATIONS);
disruptorMeanLatency[i] = histogram.getHistogramData().getMean();
System.out.format("%s run %d Disruptor %s\n", getClass().getSimpleName(), Long.valueOf(i), histogram);
dumpHistogram(histogram, System.out);
}
for (int i = 0; i < runs; i++)
{
assertTrue("run: " + i, queueMeanLatency[i] > disruptorMeanLatency[i]);
}
}
private static void dumpHistogram(Histogram histogram, final PrintStream out)
{
histogram.getHistogramData().outputPercentileDistribution(out, 1, 1.0);
}
private void runQueuePass() throws Exception
{
new Thread(stepOneQueueProcessor,"stepOneQueueProcessor").start();
new Thread(stepTwoQueueProcessor,"stepTwoQueueProcessor").start();
Thread.sleep(1000);
for (long i = 0; i < ITERATIONS; i++)
{
long t0 = System.nanoTime();
stepOneQueue.transfer(new Long(i));
Long v = null;
while (v==null) {
v = stepThreeQueue.poll();
}
long t1 = System.nanoTime();
histogram.recordValue(t1 - t0);
assertTrue(v==i);
while (PAUSE_NANOS > (System.nanoTime() - t1))
{
Thread.yield();
}
}
}
private void runDisruptorPass() throws InterruptedException
{
CountDownLatch latch = new CountDownLatch(1);
stepThreeFunctionHandler.reset(latch, stepThreeBatchProcessor.getSequence().get() + ITERATIONS);
executor.submit(stepOneBatchProcessor);
executor.submit(stepTwoBatchProcessor);
executor.submit(stepThreeBatchProcessor);
Sequence stepThreeSequence = stepThreeBatchProcessor.getSequence();
Thread.sleep(1000);
for (long i = 0; i < ITERATIONS; i++)
{
long t0 = System.nanoTime();
long sequence = ringBuffer.next();
ringBuffer.get(sequence).setValue(t0); //XXX: t0? or i?
ringBuffer.publish(sequence);
while (stepThreeSequence.get() < sequence)
{
// busy spin
}
long t1 = System.nanoTime();
// histogram.recordValue(calculateLatency(t0, t1), PAUSE_NANOS);
histogram.recordValue(calculateLatency(t0, t1));
while (PAUSE_NANOS > (System.nanoTime() - t1))
{
// busy spin
}
}
if (stepThreeSequence.get() == 0)
{
throw new IllegalStateException("Prevent hotspot optimising everything away");
}
latch.await();
stepOneBatchProcessor.halt();
stepTwoBatchProcessor.halt();
stepThreeBatchProcessor.halt();
}
private long calculateLatency(long t0, long t1)
{
return max(t1 - t0, 0);
}
public static void main(String[] args) throws Exception
{
ThrottledOnePublisherToThreeProcessorPipelineLatencyTestV2 test = new ThrottledOnePublisherToThreeProcessorPipelineLatencyTestV2();
test.shouldCompareDisruptorVsQueues();
}
}
final class LatencyQueueProcessor implements Runnable {
private final LinkedTransferQueue<Long> inputQueue;
private final LinkedTransferQueue<Long> outputQueue;
private final long count;
public LatencyQueueProcessor(final LinkedTransferQueue<Long> inputQueue,
final LinkedTransferQueue<Long> outputQueue,
final long count) {
this.inputQueue = inputQueue;
this.outputQueue = outputQueue;
this.count = count;
}
@Override
public void run() {
for (int i = 0; i < count; i++) {
try {
Long v = null;
while (v==null) {
v = inputQueue.poll();
}
outputQueue.transfer(v);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}