diff --git a/stormpot-benchmark/pom.xml b/stormpot-benchmark/pom.xml index d095d687..1602b1a5 100644 --- a/stormpot-benchmark/pom.xml +++ b/stormpot-benchmark/pom.xml @@ -14,5 +14,13 @@ stormpot-parent 2.1-SNAPSHOT + + + + ${groupId} + stormpot + ${version} + + diff --git a/stormpot-benchmark/src/main/java/stormpot/benchmark/Bench.java b/stormpot-benchmark/src/main/java/stormpot/benchmark/Bench.java new file mode 100644 index 00000000..9d3f021f --- /dev/null +++ b/stormpot-benchmark/src/main/java/stormpot/benchmark/Bench.java @@ -0,0 +1,51 @@ +package stormpot.benchmark; + +public abstract class Bench { + public abstract void primeWithSize(int size) throws Exception; + public abstract Object claim() throws Exception; + public abstract void release(Object object) throws Exception; + public abstract void claimAndRelease() throws Exception; + + private int trials; + private long timeSum; + private long timeMin = Long.MAX_VALUE; + private long timeMax = Long.MIN_VALUE; + private long period; + + public final void recordTime(long time) { + trials++; + timeSum += time; + timeMin = Math.min(timeMin, time); + timeMax = Math.max(timeMax, time); + } + + public final void recordPeriod(long period) { + this.period = period; + } + + public final void reset() { + trials = 0; + timeSum = 0; + timeMin = Long.MAX_VALUE; + timeMax = Long.MIN_VALUE; + period = 0; + } + + public final void report() { + String name = getName(); + double cyclesPerSec = (1000.0 / period) * trials; + double timeMean = ((double) timeSum) / trials; + + String str = "Benchmark: %s\t" + + "%s trials\t" + + "%.1f claim+release/sec\t" + + "latency(max, mean, min) = " + + "(% 3d, %.6f, %s) in millis.\n"; + System.out.printf( + str, name, trials, cyclesPerSec, timeMax, timeMean, timeMin); + } + + public final String getName() { + return getClass().getSimpleName(); + } +} diff --git a/stormpot-benchmark/src/main/java/stormpot/benchmark/Clock.java b/stormpot-benchmark/src/main/java/stormpot/benchmark/Clock.java new file mode 100644 index 00000000..14a51180 --- /dev/null +++ b/stormpot-benchmark/src/main/java/stormpot/benchmark/Clock.java @@ -0,0 +1,95 @@ +package stormpot.benchmark; + +import java.util.concurrent.locks.LockSupport; + +/** + * The Clock improves upon {@link System#currentTimeMillis()} in that it can be + * stopped and manually ticked forward. This is useful for testability. + * @author cvh + * + */ +public final class Clock { + + private static volatile long now; + + private static final Ticker ticker = new Ticker(); + + private Clock() { /* static utility class */ } + + /** + * Advance the clock to the current time. + * @return The new current time, in milliseconds since the epoch. + */ + public static long tick() { + return now = System.currentTimeMillis(); + } + + /** + * Advance the clock by exactly one millisecond, regardless of what the + * actual time may be. + * Note that this method is racy, and is technically only safe to call by + * a single thread at a time, and when the automatic ticking of the clock + * has been turned off. This method is only really useful for unit testing of + * time dependent code. + */ + public static void inc() { + now++; + } + + /** + * Get the value of the last tick. + * @return The "current" time recorded by the last tick. + */ + public static long currentTimeMillis() { + return now; + } + + /** + * Start automatic ticking. This will tick the clock, and update its current + * time value, about every 10 milliseconds. Note that this does not guarantee + * that the clock will get a distinctively new time value every 10 + * milliseconds. The clock can be stopped again at any time with the + * {@link #stop()} method. Starting an already started clock has no effect. + */ + public static void start() { + tick(); + ticker.stopFlag = false; + if (ticker.getState() == Thread.State.NEW) { + ticker.start(); + } else { + LockSupport.unpark(ticker); + } + } + + /** + * Stop automatic ticking. It can be resumed again with the {@link #start()} + * method. Stopping an already stopped clock has no effect. + */ + public static void stop() { + ticker.stopFlag = true; + } + + private static final class Ticker extends Thread { + public volatile boolean stopFlag; + + public Ticker() { + super("Clock-Thread"); + setDaemon(true); + } + + @Override + public void run() { + for (;;) { + if (stopFlag) { + LockSupport.park(); + } + tick(); +// LockSupport.parkNanos(1000); // 1 millis + try { + Thread.sleep(1); + } catch (InterruptedException e) { + } + } + } + } +} diff --git a/stormpot-benchmark/src/main/java/stormpot/benchmark/QueuePoolBench.java b/stormpot-benchmark/src/main/java/stormpot/benchmark/QueuePoolBench.java new file mode 100644 index 00000000..e76ef946 --- /dev/null +++ b/stormpot-benchmark/src/main/java/stormpot/benchmark/QueuePoolBench.java @@ -0,0 +1,37 @@ +package stormpot.benchmark; + +import java.util.concurrent.TimeUnit; + +import stormpot.Config; +import stormpot.Timeout; +import stormpot.qpool.QueuePool; + +public class QueuePoolBench extends Bench { + private static final Timeout timeout = + new Timeout(1000, TimeUnit.MILLISECONDS); + + private QueuePool pool; + + @Override + public void primeWithSize(int size) { + Config config = new Config(); + config.setAllocator(new StormpotPoolableAllocator()); + config.setSize(size); + pool = new QueuePool(config); + } + + @Override + public Object claim() throws Exception { + return pool.claim(timeout); + } + + @Override + public void release(Object object) throws Exception { + ((StormpotPoolable) object).release(); + } + + @Override + public void claimAndRelease() throws Exception { + release(claim()); + } +} diff --git a/stormpot-benchmark/src/main/java/stormpot/benchmark/StormpotPoolable.java b/stormpot-benchmark/src/main/java/stormpot/benchmark/StormpotPoolable.java new file mode 100644 index 00000000..64c15040 --- /dev/null +++ b/stormpot-benchmark/src/main/java/stormpot/benchmark/StormpotPoolable.java @@ -0,0 +1,17 @@ +package stormpot.benchmark; + +import stormpot.Poolable; +import stormpot.Slot; + +public class StormpotPoolable implements Poolable { + private Slot slot; + + public StormpotPoolable(Slot slot) { + this.slot = slot; + } + + @Override + public void release() { + slot.release(this); + } +} diff --git a/stormpot-benchmark/src/main/java/stormpot/benchmark/StormpotPoolableAllocator.java b/stormpot-benchmark/src/main/java/stormpot/benchmark/StormpotPoolableAllocator.java new file mode 100644 index 00000000..dc78cad1 --- /dev/null +++ b/stormpot-benchmark/src/main/java/stormpot/benchmark/StormpotPoolableAllocator.java @@ -0,0 +1,16 @@ +package stormpot.benchmark; + +import stormpot.Allocator; +import stormpot.Poolable; +import stormpot.Slot; + +public class StormpotPoolableAllocator implements Allocator { + @Override + public Poolable allocate(Slot slot) throws Exception { + return new StormpotPoolable(slot); + } + + @Override + public void deallocate(Poolable poolable) throws Exception { + } +} diff --git a/stormpot-benchmark/src/main/java/stormpot/benchmark/Throughput.java b/stormpot-benchmark/src/main/java/stormpot/benchmark/Throughput.java new file mode 100644 index 00000000..aa9c691b --- /dev/null +++ b/stormpot-benchmark/src/main/java/stormpot/benchmark/Throughput.java @@ -0,0 +1,80 @@ +package stormpot.benchmark; + +/** + * After warm-up, how many times can we claim and release non-expiring objects + * in a given timeframe? + * @author cvh + */ +public class Throughput { + private static final int SIZE = 10; + private static final long TRIAL_TIME_MILLIS = 500L; + + public static void main(String[] args) throws Exception { + Clock.start(); + System.out.println("Stormpot Single-Threaded Throughput Benchmark"); + try { + runBenchmark(); + } finally { + System.exit(0); + } + } + + private static void runBenchmark() throws Exception { + Bench queuePool = new QueuePoolBench(); + queuePool.primeWithSize(SIZE); + + warmup(queuePool); + trial(queuePool); + trial(queuePool); + trial(queuePool); + trial(queuePool); + trial(queuePool); + trial(queuePool); + trial(queuePool); + trial(queuePool); + trial(queuePool); + trial(queuePool); + } + + private static void warmup(Bench bench) throws Exception { + System.out.println("Warming up " + bench.getName()); + int steps = 20; + for (int i = 0; i < steps; i++) { + for (int j = 0; j < 1000; j++) { + benchmark(bench, 1); + } + System.out.printf("%02d/%s.", i, steps); + } + System.out.println("\nWarmup done."); + } + + private static void trial(Bench bench) throws Exception { + Thread.sleep(10); + benchmark(bench, TRIAL_TIME_MILLIS); + bench.report(); + } + + private static void benchmark(Bench bench, long trialTimeMillis) throws Exception { + bench.reset(); + + long start = Clock.currentTimeMillis(); + long deadline = start + trialTimeMillis; + long end = 0L; + do { + end = runCycles(bench, 100); + } while (end < deadline); + bench.recordPeriod(end - start); + } + + private static long runCycles(Bench bench, int cycles) throws Exception { + long start; + long end = 0; + for (int i = 0; i < cycles; i++) { + start = Clock.currentTimeMillis(); + bench.claimAndRelease(); + end = Clock.currentTimeMillis(); + bench.recordTime(end - start); + } + return end; + } +}