├── README ├── pom.xml └── src └── main └── java └── net └── shipilev ├── Disruptor.java ├── ForkJoin.java ├── ForkJoinRecursive.java ├── ForkJoinRecursiveDeep.java ├── ForkJoinReuse.java ├── SingleThread.java ├── Streams.java └── Workload.java /README: -------------------------------------------------------------------------------- 1 | (Implicitly uses JMH) 2 | 3 | Use JDK 8 GA to build and run. 4 | 5 | Build: 6 | $ mvn clean install 7 | 8 | Run: 9 | $ java -jar target/microbenchmarks.jar 10 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 25 | 27 | 4.0.0 28 | net.shipilev 29 | disrupting-fjp 30 | 1.0-SNAPSHOT 31 | jar 32 | 33 | 34 | org.openjdk.jmh 35 | jmh-core 36 | 1.6.2 37 | 38 | 39 | org.openjdk.jmh 40 | jmh-generator-annprocess 41 | 1.6.2 42 | provided 43 | 44 | 45 | com.lmax 46 | disruptor 47 | 3.3.2 48 | 49 | 50 | 51 | UTF-8 52 | 53 | 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-compiler-plugin 58 | 3.0 59 | 60 | 1.8 61 | 1.8 62 | 1.8 63 | 64 | 65 | 66 | org.apache.maven.plugins 67 | maven-shade-plugin 68 | 2.0 69 | 70 | 71 | package 72 | 73 | shade 74 | 75 | 76 | microbenchmarks 77 | 78 | 79 | org.openjdk.jmh.Main 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/main/java/net/shipilev/Disruptor.java: -------------------------------------------------------------------------------- 1 | package net.shipilev; 2 | 3 | import com.lmax.disruptor.EventFactory; 4 | import com.lmax.disruptor.EventHandler; 5 | import com.lmax.disruptor.RingBuffer; 6 | import com.lmax.disruptor.SleepingWaitStrategy; 7 | import com.lmax.disruptor.dsl.ProducerType; 8 | import org.openjdk.jmh.annotations.Benchmark; 9 | import org.openjdk.jmh.annotations.Level; 10 | import org.openjdk.jmh.annotations.Setup; 11 | import org.openjdk.jmh.annotations.TearDown; 12 | 13 | import java.util.concurrent.CountDownLatch; 14 | import java.util.concurrent.ExecutorService; 15 | import java.util.concurrent.Executors; 16 | 17 | public class Disruptor extends Workload { 18 | 19 | private ExecutorService executor; 20 | private com.lmax.disruptor.dsl.Disruptor disruptor; 21 | private RingBuffer ringBuffer; 22 | private PiResultReclaimer result; 23 | 24 | public static class PiJob { 25 | public double result; 26 | public int sliceNr; 27 | public int partitionId; 28 | 29 | public void calculatePi() { 30 | result = doCalculatePi(sliceNr); 31 | } 32 | } 33 | 34 | public static class PiEventFac implements EventFactory { 35 | 36 | @Override 37 | public PiJob newInstance() { 38 | return new PiJob(); 39 | } 40 | } 41 | 42 | public static class PiEventProcessor implements EventHandler { 43 | private final int partionId; 44 | 45 | public PiEventProcessor(final int partionId) { 46 | this.partionId = partionId; 47 | } 48 | 49 | @Override 50 | public void onEvent(final PiJob event, final long sequence, final boolean isEndOfBatch) throws Exception { 51 | if (partionId == event.partitionId) { 52 | event.calculatePi(); 53 | } 54 | } 55 | } 56 | 57 | public static class PiResultReclaimer implements EventHandler { 58 | private double result; 59 | private long seq; 60 | private final int numSlice; 61 | private final CountDownLatch latch; 62 | 63 | public PiResultReclaimer(final int numSlice) { 64 | this.numSlice = numSlice; 65 | latch = new CountDownLatch(1); 66 | } 67 | 68 | @Override 69 | public void onEvent(final PiJob event, final long sequence, final boolean isEndOfBatch) throws Exception { 70 | result += event.result; 71 | if (++seq >= numSlice) { 72 | latch.countDown(); 73 | } 74 | } 75 | 76 | public double get() throws InterruptedException { 77 | latch.await(); 78 | return result; 79 | } 80 | } 81 | 82 | @Setup(Level.Iteration) 83 | public void setup() { 84 | executor = Executors.newCachedThreadPool(); 85 | disruptor = new com.lmax.disruptor.dsl.Disruptor<>(new PiEventFac(), Integer.highestOneBit(getSlices()), 86 | executor, ProducerType.SINGLE, new SleepingWaitStrategy()); 87 | final PiEventProcessor procs[] = new PiEventProcessor[getThreads()]; 88 | result = new PiResultReclaimer(getSlices()); 89 | for (int i = 0; i < procs.length; i++) { 90 | procs[i] = new PiEventProcessor(i); 91 | } 92 | disruptor.handleEventsWith(procs).then(result); 93 | disruptor.start(); 94 | ringBuffer = disruptor.getRingBuffer(); 95 | } 96 | 97 | @TearDown(Level.Iteration) 98 | public void tearDown() { 99 | disruptor.shutdown(); 100 | executor.shutdownNow(); 101 | } 102 | 103 | @Benchmark 104 | public double run() throws InterruptedException { 105 | int ts = getThreads(); 106 | int slices = getSlices(); 107 | int partitionId = 0; 108 | for (int i = 0; i < slices; i++) { 109 | final long seq = ringBuffer.next(); 110 | final PiJob piJob = ringBuffer.get(seq); 111 | piJob.sliceNr = i; 112 | piJob.result = 0; 113 | piJob.partitionId = partitionId; 114 | ringBuffer.publish(seq); 115 | partitionId = (partitionId == (ts - 1)) ? 0 : partitionId + 1; 116 | } 117 | return result.get(); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/net/shipilev/ForkJoin.java: -------------------------------------------------------------------------------- 1 | package net.shipilev; 2 | 3 | import org.openjdk.jmh.annotations.Benchmark; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.concurrent.RecursiveTask; 8 | 9 | public class ForkJoin extends Workload { 10 | 11 | /* 12 | The fork-join task below is used as "just" the Callable. 13 | */ 14 | 15 | static class PiForkJoinTask extends RecursiveTask { 16 | private final int from; 17 | private final int to; 18 | 19 | public PiForkJoinTask(final int from, final int to) { 20 | this.from = from; 21 | this.to = to; 22 | } 23 | 24 | @Override 25 | protected Double compute() { 26 | double acc = 0; 27 | for (int s = from; s < to; s++) { 28 | acc += doCalculatePi(s); 29 | } 30 | return acc; 31 | } 32 | } 33 | 34 | @Benchmark 35 | public double run() throws InterruptedException { 36 | final List tasks = new ArrayList(); 37 | final int ts = getThreads(); 38 | final int slicePerThread = getSlices() / ts; 39 | for (int i = 0; i < ts; i++) { 40 | PiForkJoinTask task = new PiForkJoinTask(i * slicePerThread, (i + 1) * slicePerThread); 41 | task.fork(); 42 | tasks.add(task); 43 | } 44 | double acc = 0; 45 | for (PiForkJoinTask task : tasks) { 46 | acc += task.join(); 47 | } 48 | return acc; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/net/shipilev/ForkJoinRecursive.java: -------------------------------------------------------------------------------- 1 | package net.shipilev; 2 | 3 | import org.openjdk.jmh.annotations.Benchmark; 4 | 5 | import java.util.concurrent.ForkJoinTask; 6 | import java.util.concurrent.RecursiveTask; 7 | 8 | public class ForkJoinRecursive extends Workload { 9 | 10 | /* 11 | The fork-join task below recursively divides the work on slices, 12 | with some sensible decomposition threshold. 13 | */ 14 | 15 | static class PiForkJoinTask extends RecursiveTask { 16 | private final int from; 17 | private final int to; 18 | 19 | public PiForkJoinTask(final int from, final int to) { 20 | this.from = from; 21 | this.to = to; 22 | } 23 | 24 | @Override 25 | protected Double compute() { 26 | final int slices = to - from; 27 | if (slices < 10000) { 28 | double acc = 0; 29 | for (int s = from; s < to; s++) { 30 | acc += doCalculatePi(s); 31 | } 32 | return acc; 33 | } 34 | final int mid = from + slices / 2; 35 | PiForkJoinTask t1 = new PiForkJoinTask(from, mid); 36 | PiForkJoinTask t2 = new PiForkJoinTask(mid, to); 37 | ForkJoinTask.invokeAll(t1, t2); 38 | return t1.join() + t2.join(); 39 | } 40 | } 41 | 42 | @Benchmark 43 | public double run() throws InterruptedException { 44 | return new PiForkJoinTask(0, getSlices()).invoke(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/net/shipilev/ForkJoinRecursiveDeep.java: -------------------------------------------------------------------------------- 1 | package net.shipilev; 2 | 3 | import org.openjdk.jmh.annotations.Benchmark; 4 | 5 | import java.util.concurrent.ForkJoinTask; 6 | import java.util.concurrent.RecursiveTask; 7 | 8 | public class ForkJoinRecursiveDeep extends Workload { 9 | 10 | /* 11 | The fork-join task below deeply recurses, up until the leaf 12 | contains a single slice. 13 | */ 14 | 15 | static class PiForkJoinTask extends RecursiveTask { 16 | private final int from; 17 | private final int to; 18 | 19 | public PiForkJoinTask(final int from, final int to) { 20 | this.from = from; 21 | this.to = to; 22 | } 23 | 24 | @Override 25 | protected Double compute() { 26 | final int slices = to - from; 27 | if (slices <= 1) { 28 | double acc = 0; 29 | for (int s = from; s < to; s++) { 30 | acc += doCalculatePi(s); 31 | } 32 | return acc; 33 | } 34 | final int mid = from + slices / 2; 35 | PiForkJoinTask t1 = new PiForkJoinTask(from, mid); 36 | PiForkJoinTask t2 = new PiForkJoinTask(mid, to); 37 | ForkJoinTask.invokeAll(t1, t2); 38 | return t1.join() + t2.join(); 39 | } 40 | } 41 | 42 | @Benchmark 43 | public double run() throws InterruptedException { 44 | return new PiForkJoinTask(0, getSlices()).invoke(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/net/shipilev/ForkJoinReuse.java: -------------------------------------------------------------------------------- 1 | package net.shipilev; 2 | 3 | import org.openjdk.jmh.annotations.Benchmark; 4 | 5 | import java.util.concurrent.RecursiveTask; 6 | 7 | public class ForkJoinReuse extends Workload { 8 | 9 | /* 10 | The fork-join task below is used as "just" the Callable, 11 | and "reuses" the submitted tasks. 12 | */ 13 | 14 | static class PiForkJoinTask extends RecursiveTask { 15 | private int slice; 16 | 17 | @Override 18 | protected Double compute() { 19 | return doCalculatePi(slice); 20 | } 21 | } 22 | 23 | @Benchmark 24 | public double run() throws InterruptedException { 25 | final int stride = getThreads() * 100; 26 | final PiForkJoinTask[] tasks = new PiForkJoinTask[stride]; 27 | for (int i = 0; i < stride; i++) { 28 | PiForkJoinTask task = new PiForkJoinTask(); 29 | task.slice = i; 30 | task.fork(); 31 | tasks[i] = task; 32 | } 33 | double acc = 0; 34 | final int s1 = getSlices() / stride; 35 | for (int i = 1; i < s1; i++) { 36 | for (PiForkJoinTask task : tasks) { 37 | acc += task.join(); 38 | task.reinitialize(); 39 | task.slice += stride; 40 | task.fork(); 41 | } 42 | } 43 | for (PiForkJoinTask task : tasks) { 44 | acc += task.join(); 45 | } 46 | final int s2 = getSlices() % stride; 47 | for (int i = 0; i < s2; i++) { 48 | final PiForkJoinTask task = tasks[i]; 49 | task.reinitialize(); 50 | task.slice += stride; 51 | task.fork(); 52 | } 53 | for (int i = 0; i < s2; i++) { 54 | acc += tasks[i].join(); 55 | } 56 | return acc; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/net/shipilev/SingleThread.java: -------------------------------------------------------------------------------- 1 | package net.shipilev; 2 | 3 | import org.openjdk.jmh.annotations.Benchmark; 4 | 5 | public class SingleThread extends Workload { 6 | 7 | @Benchmark 8 | public double run() { 9 | double acc = 0; 10 | for (int s = 0; s < getSlices(); s++) { 11 | acc += doCalculatePi(s); 12 | } 13 | return acc; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/net/shipilev/Streams.java: -------------------------------------------------------------------------------- 1 | package net.shipilev; 2 | 3 | import org.openjdk.jmh.annotations.Benchmark; 4 | 5 | import java.util.stream.IntStream; 6 | 7 | public class Streams extends Workload { 8 | 9 | @Benchmark 10 | public double run() { 11 | return IntStream.range(0, getSlices()) 12 | .parallel() 13 | .mapToDouble(Workload::doCalculatePi) 14 | .reduce(0, Double::sum); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/shipilev/Workload.java: -------------------------------------------------------------------------------- 1 | package net.shipilev; 2 | 3 | import org.openjdk.jmh.annotations.BenchmarkMode; 4 | import org.openjdk.jmh.annotations.Fork; 5 | import org.openjdk.jmh.annotations.Measurement; 6 | import org.openjdk.jmh.annotations.Mode; 7 | import org.openjdk.jmh.annotations.OutputTimeUnit; 8 | import org.openjdk.jmh.annotations.Param; 9 | import org.openjdk.jmh.annotations.Scope; 10 | import org.openjdk.jmh.annotations.State; 11 | import org.openjdk.jmh.annotations.Warmup; 12 | 13 | import java.util.concurrent.TimeUnit; 14 | 15 | @State(Scope.Benchmark) 16 | @Warmup(iterations = 5) 17 | @Measurement(iterations = 10) 18 | @Fork(5) 19 | @BenchmarkMode(Mode.SingleShotTime) 20 | @OutputTimeUnit(TimeUnit.MILLISECONDS) 21 | public class Workload { 22 | 23 | @Param({"500", "1000", "5000", "10000", "50000"}) 24 | public static int slicesK; 25 | 26 | @Param({"10"}) 27 | public static int workMult; 28 | 29 | @Param("0") 30 | public static int threads; 31 | 32 | static int getThreads() { 33 | if (threads == 0) { 34 | return Runtime.getRuntime().availableProcessors(); 35 | } else { 36 | return threads; 37 | } 38 | } 39 | 40 | static int getSlices() { 41 | return slicesK * 1000; 42 | } 43 | 44 | public static double doCalculatePi(final int sliceNr) { 45 | final int from = sliceNr * workMult; 46 | final int to = from + workMult; 47 | final int c = (to << 1) + 1; 48 | double acc = 0; 49 | for (int a = 4 - ((from & 1) << 3), b = (from << 1) + 1; b < c; a = -a, b += 2) { 50 | acc += ((double) a) / b; 51 | } 52 | return acc; 53 | } 54 | 55 | } 56 | --------------------------------------------------------------------------------