/**
* 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 z.testware.common.Stopwatch;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedTransferQueue;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import static com.lmax.disruptor.RingBuffer.createSingleProducer;
import static z.util.Contracts.contract;
import static z.util.Throwables.uncheck;
/**
*/
public class RoundTripThroughputTest {
private LinkedTransferQueue<Integer> ltqC = new LinkedTransferQueue();
private LinkedTransferQueue<Integer> ltqP = new LinkedTransferQueue();
private static final int BUFFER_SIZE = 1024;
private RingBuffer<ValueEvent> ringBufferC =
createSingleProducer(ValueEvent.EVENT_FACTORY, BUFFER_SIZE, new YieldingWaitStrategy());
private RingBuffer<ValueEvent> ringBufferP =
createSingleProducer(ValueEvent.EVENT_FACTORY, BUFFER_SIZE, new YieldingWaitStrategy());
private ConcurrentLinkedQueue<Integer> clqC = new ConcurrentLinkedQueue();
private ConcurrentLinkedQueue<Integer> clqP = new ConcurrentLinkedQueue();
private static final int RUNS = 10_000_000;
private static final Integer[] preallocatedInts = new Integer[RUNS+1];
static {
IntStream.range(0, RUNS+1).forEach((i) ->
preallocatedInts[i] = new Integer(i)
);
System.out.println("pre-allocation done.");
}
@Ignore
@Test
public void testLTQ() {
runFinePrint("for LinkedTransferQueue", () -> runLTQ());
}
public void runLTQ() {
int numRunnings0 = numOfRunningThreads();
List<Integer> bufferC = new ArrayList<>(2024 * 2048);
List<Integer> bufferP = new ArrayList<>(2024 * 2048);
//consumer thread
new Thread(() -> {
IntStream.range(0, RUNS).forEach((i) -> {
try {
int num = 0;
while (num == 0) {
num = ltqC.drainTo(bufferC);
Thread.yield();
}
for (int k = 0; k < num; k++) {//XXX: num guarantee the last available index?
ltqP.put(preallocatedInts[bufferC.get(k).intValue() + 1]);
}
bufferC.clear();
} catch (Exception e) {
e.printStackTrace();
}
});
}).start();
//producer is in main thread?
Stopwatch watch = Stopwatch.create();
watch.start();
// LongStream.range(0, RUNS).forEach((i) ->
for (int p = 0; p < RUNS; p++) {
try {
ltqC.put(preallocatedInts[p]);
} catch (Exception e) {
e.printStackTrace();
}
}
// watch.print();
// );
//XXX: fetch after put?
int count = 0;
while (count != RUNS) {
int num = 0;
while (num == 0) {
num = ltqP.drainTo(bufferP);
}
// for (int k = 0; k < num; k++) {
// final long checked = bufferP.get(k).longValue();
// }
bufferP.clear();
count += num;
}
watch.stop();
long opsPerSecond = RUNS * 1000_000_000L / watch.elapsed();//XXX: *2 for EndToEnd?
System.out.printf("%,d ops/sec\n", opsPerSecond);
contract(() -> ltqC.size() == 0);
contract(() -> ltqP.size() == 0);
contract(() -> numOfRunningThreads() == numRunnings0);
}
@Test
public void testCLQ() {
runFinePrint("for ConcurrentLinkedQueue", () -> runCLQ());
}
public void runCLQ() {
int numRunnings0 = numOfRunningThreads();
//consumer thread
new Thread(() -> {
LongStream.range(0, RUNS).forEach((i) -> {
try {
Integer v = null;
while (v == null) {
v = clqC.poll();
}
clqP.offer(preallocatedInts[v.intValue() + 1]);
} catch (Exception e) {
e.printStackTrace();
}
});
}).start();
//producer is in main thread?
Stopwatch watch = Stopwatch.create();
watch.start();
// LongStream.range(0, RUNS).forEach((i) ->
for (int i = 0; i < RUNS; i++) {
try {
clqC.offer(preallocatedInts[i]);
} catch (Exception e) {
e.printStackTrace();
}
}
// watch.print();
// );
//XXX: fetch after put?
int count = 0;
for (int i = 0; i < RUNS; i++) {
Integer v = clqP.poll();
// final int checked = i;
// contract(()-> v==(checked+1));
}
watch.stop();
// watch.print();
long opsPerSecond = RUNS * 1000_000_000L / watch.elapsed();//XXX: *2 for EndToEnd?
System.out.printf("%,d ops/sec\n", opsPerSecond);
contract(() -> clqC.size() == 0);
contract(() -> clqP.size() == 0);
contract(() -> numOfRunningThreads() == numRunnings0);
}
@Test
public void testDisruptor() {
runFinePrint("for Disruptor", () -> runDisruptor());
}
private void runDisruptor() {
BatchEventProcessor<ValueEvent> processorC = new BatchEventProcessor<>(//BUG?: can not infer type if no diamond
ringBufferC,
ringBufferC.newBarrier(),
(event, sequence, endOfBatch) -> {
// Publishers claim events in sequence
// long sequence = ringBufferP.next();
ringBufferP.get(sequence).setValue(preallocatedInts[event.getValue().intValue() + 1]);
ringBufferP.publish(sequence);
}
);
// Each processor runs on a separate thread
new Thread(processorC).start();
final SequenceBarrier sbP = ringBufferP.newBarrier();
Stopwatch watch = Stopwatch.create();
watch.start();
IntStream.range(0, RUNS).forEach((i) -> {
// Publishers claim events in sequence
long seqC = ringBufferC.next();
ValueEvent event = ringBufferC.get(seqC);
event.setValue(preallocatedInts[i]);
// publish the event
ringBufferC.publish(seqC);
});
//
long availableSequence = -1;
// while (availableSequence != RUNS - 1) {
// availableSequence = sbP.getCursor();
// }
for (int i = 0; i < RUNS; i++) {
try {
availableSequence = sbP.waitFor(i);
}catch (Exception e){
e.printStackTrace();
}
}
watch.stop();
long opsPerSecond = RUNS * 1000_000_000L / watch.elapsed();//XXX: *2 for EndToEnd?
System.out.printf("%,d ops/sec\n", opsPerSecond);
}
/**
* NOTE: the state of RUNNABLE or not is not important here
*
* @return
*/
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 EndToEndThroughputTestForLongHyperLoop().testLTQ();
// new EndToEndThroughputTestForLongHyperLoop().testCLQ();
new RoundTripThroughputTest().testDisruptor();
}
static final class ValueEvent {
private Integer value;
public Integer getValue() {
return value;
}
public void setValue(final Integer value) {
this.value = value;
}
public static final EventFactory<ValueEvent> EVENT_FACTORY = new EventFactory<ValueEvent>() {
public ValueEvent newInstance() {
return new ValueEvent();
}
};
}
}