/** * 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.PaddedLong; import org.junit.Assert; import org.junit.Test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedTransferQueue; import static com.lmax.disruptor.RingBuffer.createSingleProducer; /** * <pre> * UniCast a series of items between 1 publisher and 1 event processor. * * +----+ +-----+ * | P1 |--->| EP1 | * +----+ +-----+ * * * Queue Based: * ============ * * put take * +----+ +====+ +-----+ * | P1 |--->| Q1 |<---| EP1 | * +----+ +====+ +-----+ * * P1 - Publisher 1 * Q1 - Queue 1 * EP1 - EventProcessor 1 * * * Disruptor: * ========== * track to prevent wrap * +------------------+ * | | * | v * +----+ +====+ +====+ +-----+ * | P1 |--->| RB |<---| SB | | EP1 | * +----+ +====+ +====+ +-----+ * claim get ^ | * | | * +--------+ * waitFor * * P1 - Publisher 1 * RB - Hyperloop * SB - SequenceBarrier * EP1 - EventProcessor 1 * * </pre> */ public final class OnePublisherToOneProcessorUniCastThroughputTest { //==================AbstractPerfTestQueueVsDisruptor============================= public static final int RUNS = 5; protected void testImplementations() throws Exception { final int availableProcessors = Runtime.getRuntime().availableProcessors(); if (getRequiredProcessorCount() > availableProcessors) { System.out.print("*** Warning ***: your system has insufficient processors to execute the test efficiently. "); System.out.println("Processors required = " + getRequiredProcessorCount() + " available = " + availableProcessors); } long[] queueOps = new long[RUNS]; long[] disruptorOps = new long[RUNS]; if ("true".equalsIgnoreCase(System.getProperty("com.lmax.runQueueTests", "false"))) { System.out.println("Starting Queue tests..."); for (int i = 0; i < RUNS; i++) { System.gc(); queueOps[i] = runQueuePass(); System.out.format("Run %d, TransferQueue=%,d ops/sec%n", i, Long.valueOf(queueOps[i])); } } else { System.out.println("Skipping Queue tests"); } System.out.println("Starting Disruptor tests..."); for (int i = 0; i < RUNS; i++) { System.gc(); disruptorOps[i] = runDisruptorPass(); System.out.format("Run %d, Disruptor=%,d ops/sec%n", i, Long.valueOf(disruptorOps[i])); } // printResults(getClass().getSimpleName(), disruptorOps, queueOps); for (int i = 0; i < RUNS; i++) { Assert.assertTrue("Performance degraded", disruptorOps[i] > queueOps[i]); } } public static void printResults(final String className, final long[] disruptorOps, final long[] queueOps) { for (int i = 0; i < RUNS; i++) { System.out.format("%s run %d: BlockingQueue=%,d Disruptor=%,d ops/sec\n", className, Integer.valueOf(i), Long.valueOf(queueOps[i]), Long.valueOf(disruptorOps[i])); } } public static long accumulatedAddition(final long iterations) { long temp = 0L; for (long i = 0L; i < iterations; i++) { temp += i; } return temp; } private static final int BUFFER_SIZE = 1024*2; //XXX: 4K:) private static final long ITERATIONS = 1000L * 1000L * 100L;//XXX: decrease for testing private final long expectedResult = accumulatedAddition(ITERATIONS); /////////////////////////////////////////////////////////////////////////////////////////////// private final LinkedTransferQueue<Long> transferQueue = new LinkedTransferQueue<>();//new LinkedBlockingQueue<Long>(BUFFER_SIZE); private final ValueAdditionProcessorOfQueue queueProcessor = new ValueAdditionProcessorOfQueue(transferQueue, ITERATIONS - 1); /////////////////////////////////////////////////////////////////////////////////////////////// private final RingBuffer<ValueEvent> ringBuffer = createSingleProducer(ValueEvent.EVENT_FACTORY, BUFFER_SIZE, new YieldingWaitStrategy()); private final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(); private final ValueAdditionEventHandlerForDisruptor handler = new ValueAdditionEventHandlerForDisruptor(); private final BatchEventProcessor<ValueEvent> batchEventProcessor = new BatchEventProcessor<ValueEvent>(ringBuffer, sequenceBarrier, handler); { ringBuffer.addGatingSequences(batchEventProcessor.getSequence()); } /////////////////////////////////////////////////////////////////////////////////////////////// protected int getRequiredProcessorCount() { return 2; } @Test public void shouldCompareDisruptorVsQueues() throws Exception { testImplementations(); } protected long runQueuePass() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); queueProcessor.reset(latch); new Thread(queueProcessor).start(); long start = System.currentTimeMillis(); for (long i = 0; i < ITERATIONS; i++) { transferQueue.put(new Long(i));//XXX: transfer -> put will improve throughput } latch.await(); long opsPerSecond = (ITERATIONS * 1000L) / (System.currentTimeMillis() - start); queueProcessor.halt(); Assert.assertEquals(expectedResult, queueProcessor.getValue()); return opsPerSecond; } protected long runDisruptorPass() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); long expectedCount = batchEventProcessor.getSequence().get() + ITERATIONS; handler.reset(latch, expectedCount); new Thread(batchEventProcessor).start(); long start = System.currentTimeMillis(); final RingBuffer<ValueEvent> rb = ringBuffer; for (long i = 0; i < ITERATIONS; i++) { long next = rb.next(); rb.get(next).setValue(i); rb.publish(next); } latch.await(); long opsPerSecond = (ITERATIONS * 1000L) / (System.currentTimeMillis() - start); waitForEventProcessorSequence(expectedCount); batchEventProcessor.halt(); Assert.assertEquals(expectedResult, handler.getValue()); return opsPerSecond; } private void waitForEventProcessorSequence(long expectedCount) throws InterruptedException { while (batchEventProcessor.getSequence().get() != expectedCount) { Thread.sleep(1); } } public static void main(String[] args) throws Exception { OnePublisherToOneProcessorUniCastThroughputTest test = new OnePublisherToOneProcessorUniCastThroughputTest(); test.shouldCompareDisruptorVsQueues(); } } final class ValueAdditionProcessorOfQueue implements Runnable { private volatile boolean running; private long value; private long sequence; private CountDownLatch latch; private final LinkedTransferQueue<Long> transferQueue; private final long count; public ValueAdditionProcessorOfQueue(final LinkedTransferQueue<Long> transferQueue, final long count) { this.transferQueue = transferQueue; this.count = count; } public long getValue() { return value; } public void reset(final CountDownLatch latch) { value = 0L; sequence = 0L; this.latch = latch; } public void halt() { running = false; } @Override public void run() { running = true; while (running) { Long v = null; while (v==null) { v = transferQueue.poll(); } this.value += v.longValue(); if (sequence++ == count) { latch.countDown(); } } } } final class ValueAdditionEventHandlerForDisruptor implements EventHandler<ValueEvent> { private final PaddedLong value = new PaddedLong(); private long count; private CountDownLatch latch; private long localSequence = -1; public long getValue() { return value.get(); } public void reset(final CountDownLatch latch, final long expectedCount) { value.set(0L); this.latch = latch; count = expectedCount; } @Override public void onEvent(final ValueEvent event, final long sequence, final boolean endOfBatch) throws Exception { value.set(value.get() + event.getValue()); if (localSequence + 1 == sequence) { localSequence = sequence; } else { System.err.println("Expected: " + (localSequence + 1) + "found: " + sequence); } if (count == sequence) { latch.countDown(); } } }