/**
* 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 org.HdrHistogram.Histogram;
import org.junit.Ignore;
import org.junit.Test;
import java.io.PrintStream;
import java.util.concurrent.*;
import java.util.stream.IntStream;
import static com.lmax.disruptor.RingBuffer.createSingleProducer;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static z.util.Throwables.uncheck;
/**
*/
public class RoundTripLatencyTest {
private static final Histogram HISTOGRAM = new Histogram(2000_000_000L, 0);
private LinkedTransferQueue<Long> ltqC = new LinkedTransferQueue();
private LinkedTransferQueue<Long> ltqP = new LinkedTransferQueue();
private SynchronousQueue<Long> sqC = new SynchronousQueue();
private SynchronousQueue<Long> sqP = new SynchronousQueue();
private static final int BUFFER_SIZE = 2;//XXX: or 1/1024, not significant
private RingBuffer<ValueEvent> ringBufferC =
createSingleProducer(ValueEvent.EVENT_FACTORY, BUFFER_SIZE, new YieldingWaitStrategy());
private RingBuffer<ValueEvent> ringBufferP =
createSingleProducer(ValueEvent.EVENT_FACTORY, BUFFER_SIZE, new YieldingWaitStrategy());
//XXX: ABQ/CLQ, not appropriate for one-shot latency measuring, just placeholder
private ArrayBlockingQueue<Long> abq = new ArrayBlockingQueue(1024);
private ConcurrentLinkedQueue<Long> clq = new ConcurrentLinkedQueue();
private static final int RUNS = 10_000_000;
private static final Long[] preallocatedLongs = new Long[RUNS];
static {
IntStream.range(0,RUNS).forEach((i) ->
preallocatedLongs[i] = new Long(i)
);
System.out.println("pre-allocation done.");
}
@Test
public void testLTQ() {
runFinePrint("for LinkedTransferQueue", () -> runLTQ());
}
public void runLTQ() {
int numRunnings0 = numOfRunningThreads();
HISTOGRAM.reset();
new Thread(() -> {
IntStream.range(0, RUNS).forEach((i) -> {
try {
Long v = null;
while (v == null) {
v = ltqC.poll();
}
ltqP.transfer(v);
} catch (Exception e) {
e.printStackTrace();
}
});
}).start();
//XXX: producer is in main thread
IntStream.range(0, RUNS).forEach((i) ->
uncheck(() -> {
long t = System.nanoTime();
ltqC.transfer(preallocatedLongs[i]);
Long v = null;
while (v == null) {
v = ltqP.poll();
}
HISTOGRAM.recordValue(System.nanoTime() - t);
assertThat(v.longValue(), is((long) i));
})
);
assertThat(ltqC.size(), is(0));
assertThat(ltqP.size(), is(0));
assertThat(numOfRunningThreads(), is(numRunnings0));
dumpHistogram(HISTOGRAM, System.out);
}
@Ignore
@Test
public void testSQ() {
runFinePrint("for SynchronousQueue", () -> runSQ());
}
public void runSQ() {
int numRunnings0 = numOfRunningThreads();
HISTOGRAM.reset();
new Thread(() -> {
IntStream.range(0, RUNS).forEach((i) ->
uncheck(() -> sqP.put(sqC.take()))
);
}).start();
//XXX: producer is in main thread
IntStream.range(0, RUNS).forEach((i) ->
uncheck(() -> {
long t = System.nanoTime();
sqC.put(preallocatedLongs[i]);
Long v = sqP.take();
HISTOGRAM.recordValue(System.nanoTime() - t);
assertThat(v.longValue(), is((long) i));
})
);
assertThat(sqC.size(), is(0));
assertThat(sqP.size(), is(0));
assertThat(numOfRunningThreads(), is(numRunnings0));
dumpHistogram(HISTOGRAM, System.out);
}
@Test
public void testDisruptor() {
runFinePrint("for Disruptor", () -> runDisruptor());
}
private void runDisruptor() {
int numRunnings0 = numOfRunningThreads();
HISTOGRAM.reset();
BatchEventProcessor<ValueEvent> processorC = new BatchEventProcessor<>(//BUG: can not inference type if no diamond
ringBufferC,
ringBufferC.newBarrier(),
(event, sequence, endOfBatch) -> {
// Publishers claim events in sequence
// long sequence = ringBufferP.next();
ringBufferP.get(sequence).setValue(event.getValue());
ringBufferP.publish(sequence);
}
);
// Each processor runs on a separate thread
new Thread(processorC).start();
final SequenceBarrier sbP = ringBufferP.newBarrier();
IntStream.range(0, RUNS).forEach((i) -> {
long t = System.nanoTime();
// Publishers claim events in sequence
long seqC = ringBufferC.next();
ValueEvent event = ringBufferC.get(seqC);
event.setValue(preallocatedLongs[i]);
// publish the event
ringBufferC.publish(seqC);
//
try {
long availableSequence = sbP.waitFor(i);
ValueEvent e = ringBufferP.get(availableSequence);
HISTOGRAM.recordValue(System.nanoTime() - t);
assertThat(availableSequence, is((long) i));
assertThat(e.getValue(), is((long) i));
} catch (final Exception e) {
throw new RuntimeException(e);//just rethrow
}
});
// assertThat(numOfRunningThreads(), is(numRunnings0));
dumpHistogram(HISTOGRAM, System.out);
}
private static final int numOfRunningThreads() {
return (int)
Thread.getAllStackTraces().keySet().stream()
.filter(t -> t.getState() == Thread.State.RUNNABLE)
.count();
}
private static void dumpHistogram(final Histogram histogram, final PrintStream out) {
histogram.getHistogramData().outputPercentileDistribution(out, 1, 1.0);
}
private static void runFinePrint(String label, Runnable runnable) {
System.out.println("================================");
System.out.println(label + " start...");
runnable.run();
System.out.println(label + " done.");
System.out.println("================================");
}
public static void main(String[] args) {
new RoundTripLatencyTest().testLTQ();
new RoundTripLatencyTest().testDisruptor();
}
static final class ValueEvent {
private Long value;
public Long getValue() {
return value;
}
public void setValue(final Long value) {
this.value = value;
}
public static final EventFactory<ValueEvent> EVENT_FACTORY = new EventFactory<ValueEvent>() {
public ValueEvent newInstance() {
return new ValueEvent();
}
};
}
}