/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 bes.injector.microbench;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.BusySpinWaitStrategy;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.TimeValue;
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@Threads(1)
@State(Scope.Benchmark)
public class ExecutorBenchmark
{
private ExecutorPlus[] exec;
private Semaphore workGate;
private int opWorkTokens;
private long opSleepNanos;
private float opSleepChance;
@Param({"128"})
private int threads;
@Param({"1:1"})
// (in):(queued) both as multiples of thread count
private String tasks;
@Param({"INJECTOR"})
private String type;
@Param({"0.1"})
private double opWork;
@Param({"-1"})
private double opWorkRatio;
@Param({"0/0"})
// (chance of sleep)/(length of sleep (us))
private String opSleep;
@Param({"1"})
private int executorChainLength;
@Setup
public void setup()
{
if (opWorkRatio < 0)
throw new IllegalStateException();
String[] opSleepArgs = opSleep.split("/");
opSleepChance = Float.parseFloat(opSleepArgs[0]);
opSleepNanos = Long.parseLong(opSleepArgs[1]) * 1000L;
opWorkTokens = (int) Math.ceil(opWork * opWorkRatio * (1d / executorChainLength));
String[] taskArgs = tasks.split(":");
int concurrentRequests = (int) (threads * Double.parseDouble(taskArgs[0]));
int maxTasksQueued = (int) (threads * Double.parseDouble(taskArgs[1]));
final InjectorPlus injector = new InjectorPlus("");
exec = new ExecutorPlus[executorChainLength];
workGate = new Semaphore(concurrentRequests, false);
for (int i = 0 ; i < exec.length ; i++)
{
switch (ExecutorType.valueOf(type))
{
case INJECTOR:
exec[i] = injector.newExecutor(threads, maxTasksQueued);
break;
case JDK:
exec[i] = new BlockingThreadPoolExecutor(threads, maxTasksQueued);
break;
case FJP:
exec[i] = new BlockingForkJoinPool(threads, maxTasksQueued);
break;
case DISRUPTOR_SPIN:
exec[i] = new DisruptorExecutor(threads, maxTasksQueued, new BusySpinWaitStrategy());
break;
case DISRUPTOR_BLOCK:
exec[i] = new DisruptorExecutor(threads, maxTasksQueued, new BlockingWaitStrategy());
break;
}
}
}
private static double calcWorkRatio(long minMeasurementIntervalNanos)
{
int tokens = 0;
long start = System.nanoTime();
long end;
do
{
tokens += 10000;
Blackhole.consumeCPU(10000);
} while ((end = System.nanoTime()) - start < minMeasurementIntervalNanos);
return tokens / (((double)(end - start)) / 1000);
}
@Benchmark
public void test()
{
workGate.acquireUninterruptibly();
exec[0].execute(new Work(1));
}
private final class Work implements Runnable
{
final int executorIndex;
private Work(int executorIndex)
{
this.executorIndex = executorIndex;
}
public void run()
{
Blackhole.consumeCPU(opWorkTokens);
if (executorIndex < exec.length)
{
exec[executorIndex].maybeExecuteInline(new Work(executorIndex + 1));
}
else
{
if (opSleepNanos > 0 && ThreadLocalRandom.current().nextFloat() < opSleepChance)
LockSupport.parkNanos(opSleepNanos);
workGate.release();
}
}
}
public static void main(String[] args) throws RunnerException, InterruptedException
{
boolean addPerf = false;
Map<String, Integer> jmhParams = new HashMap<String, Integer>();
jmhParams.put("forks", 1);
jmhParams.put("producerThreads", 1);
jmhParams.put("warmups", 5);
jmhParams.put("warmupLength", 1);
jmhParams.put("measurements", 5);
jmhParams.put("measurementLength", 2);
Map<String, String[]> benchParams = new LinkedHashMap<String, String[]>();
benchParams.put("opSleep", new String[] { "0/0" });
benchParams.put("opWork", new String[] { "1", "10", "100" });
benchParams.put("tasks", new String[] { "0.5:1", "1:1", "4:1", "4:4" });
benchParams.put("type", new String[] { "INJECTOR", "JDK" });
benchParams.put("threads", new String[] { "32", "128", "512" });
benchParams.put("executorChainLength", new String[] { "1" });
for (String arg : args)
{
if (arg.equals("-perf"))
{
addPerf = true;
continue;
}
String[] split = arg.split("=");
if (split.length != 2)
throw new IllegalArgumentException(arg + " malformed");
if (jmhParams.containsKey(split[0]))
jmhParams.put(split[0], Integer.parseInt(split[1]));
else if (benchParams.containsKey(split[0]))
benchParams.put(split[0], split[1].split(","));
else
throw new IllegalArgumentException(arg + " unknown property");
}
double workRatio = calcWorkRatio(TimeUnit.SECONDS.toNanos(1));
ChainedOptionsBuilder builder = new OptionsBuilder()
.include(".*ExecutorBenchmark.*")
.forks(jmhParams.get("forks"))
.threads(jmhParams.get("producerThreads"))
.warmupIterations(jmhParams.get("warmups"))
.warmupTime(TimeValue.seconds(jmhParams.get("warmupLength")))
.measurementIterations(jmhParams.get("measurements"))
.measurementTime(TimeValue.seconds(jmhParams.get("measurementLength")))
.jvmArgs("-dsa", "-da")
.param("opWorkRatio", String.format("%.3f", workRatio));
if (addPerf)
builder.addProfiler(org.openjdk.jmh.profile.LinuxPerfProfiler.class);
System.out.println("Running with:");
System.out.println(jmhParams);
for (Map.Entry<String, String[]> e : benchParams.entrySet())
{
System.out.println(e.getKey() + ": " + Arrays.toString(e.getValue()));
builder.param(e.getKey(), e.getValue());
}
new Runner(builder.build()).run();
}
}