meme.singularsyntax.benchmark.Benchmark.java Source code

Java tutorial

Introduction

Here is the source code for meme.singularsyntax.benchmark.Benchmark.java

Source

/*
 * Benchmark.java
 * 
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  The author designates this
 * particular file as subject to the "Classpath" exception as provided
 * for in the LICENSE file that accompanied this code.
 * 
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Copyright (c) 2013 Stephen J. Scheck. All rights reserved.
 * 
 */

package meme.singularsyntax.benchmark;

import gnu.getopt.Getopt;
import gnu.getopt.LongOpt;

import java.security.MessageDigest;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import meme.singularsyntax.benchmark.RandomByteService.RandomByteServiceCallback;
import meme.singularsyntax.benchmark.RandomByteService.Request;
import meme.singularsyntax.concurrency.ContinuationExecutorCompletionService;

import org.apache.commons.javaflow.Continuation;
import org.slf4j.LoggerFactory;

/**
 * Benchmark utility to compare performance between continuation and synchronous
 * models for simulated network I/O using ContinuableFutureTask and ordinary
 * java.util.concurrent.FutureTask.
 * 
 * @author sscheck
 * 
 */
public class Benchmark {
    private static enum ExecutionStrategy {
        SYNCHRONOUS, CONTINUATION
    }

    private static enum WorkQueueStrategy {
        SYNCHRONOUS, BOUNDED, UNBOUNDED
    }

    private static interface ServiceDispatch {
        public byte[] sendRequest(Callable<byte[]> task, Request request);

        public void recvResponse(Callable<byte[]> task, Request request, byte[] response, Throwable ex);
    }

    private static class SynchronousDispatch implements ServiceDispatch {

        private final RandomByteService randomByteService;

        public SynchronousDispatch(RandomByteService randomByteService) {
            this.randomByteService = randomByteService;
        }

        @Override
        public byte[] sendRequest(Callable<byte[]> task, Request request) {
            randomByteService.queueRequest(request);

            synchronized (request) {
                while (request.getData() == null) {
                    try {
                        request.wait();
                    } catch (InterruptedException e) {
                        // restore thread interrupt flag
                        Thread.currentThread().interrupt();
                    }
                }
            }

            return request.getData();
        }

        @Override
        public void recvResponse(Callable<byte[]> task, Request request, byte[] response, Throwable ex) {

            synchronized (request) {
                request.setData(response);
                request.notify();
            }
        }
    }

    private static class ContinuationDispatch implements ServiceDispatch {

        private final CompletionService<byte[]> completionService;
        private final RandomByteService randomByteService;

        public ContinuationDispatch(RandomByteService randomByteService,
                CompletionService<byte[]> completionService) {

            this.randomByteService = randomByteService;
            this.completionService = completionService;
        }

        @Override
        public byte[] sendRequest(Callable<byte[]> task, Request request) {
            randomByteService.queueRequest(request);

            LoggerFactory.getLogger("Javaflow")
                    .info(String.format("SUSP: task %s [%s]", task.toString(), Thread.currentThread().getName()));
            Continuation.suspend();

            return request.getData();
        }

        @Override
        public void recvResponse(Callable<byte[]> task, Request request, byte[] response, Throwable ex) {

            request.setData(response);
            LoggerFactory.getLogger("Javaflow")
                    .info(String.format("RSUB: task %s [%s]", task.toString(), Thread.currentThread().getName()));
            completionService.submit(task);
        }
    }

    private static class Task implements Callable<byte[]>, RandomByteServiceCallback {

        private final int computeSteps;
        private final ServiceDispatch dispatch;

        public Task(int computeSteps, ServiceDispatch dispatch) {
            this.computeSteps = computeSteps;
            this.dispatch = dispatch;
        }

        @Override
        public byte[] call() throws Exception {
            byte[] data = null;
            MessageDigest md = MessageDigest.getInstance("SHA-1");

            for (int ii = 0; ii < computeSteps; ii++) {
                data = dispatch.sendRequest(this, new Request(this));
                md.update(data);
            }

            return md.digest();
        }

        @Override
        public void response(Request request, byte[] response, Throwable ex) {
            dispatch.recvResponse(this, request, response, ex);
        }
    }

    private static class Application {

        private static final ExecutionStrategy DEFAULT_EXECUTION_STRATEGY = ExecutionStrategy.SYNCHRONOUS;
        private static final WorkQueueStrategy DEFAULT_WORK_QUEUE_STRATEGY = WorkQueueStrategy.SYNCHRONOUS;

        private static final int DEFAULT_WORK_QUEUE_CAPACITY = 100;
        private static final int DEFAULT_CORE_POOL_SIZE = 100;
        private static final long DEFAULT_KEEP_ALIVE_TIME = 600; // 10 minutes
        private static final int DEFAULT_REQUESTS_PER_SECOND = 10;
        private static final int DEFAULT_RUN_TIME_IN_SECONDS = 60;
        private static final int DEFAULT_MAXIMUM_POOL_SIZE = DEFAULT_REQUESTS_PER_SECOND
                * DEFAULT_RUN_TIME_IN_SECONDS;

        private static final int DEFAULT_COMPUTE_STEPS = 10;
        private static final long DEFAULT_THREAD_STACK_SIZE = 1024 * 1024; // 1 megabyte

        private static final boolean DEFAULT_QUIET_OUTPUT = false;

        private CompletionService<byte[]> completionService;
        private RandomByteService randomByteService;
        private ServiceDispatch serviceDispatch;
        private ThreadPoolExecutor threadPool;
        private ThreadFactory threadFactory;

        private int requestsPerSecond;
        private int runTime;
        private int computeSteps;
        private long threadStackSize;

        private boolean quiet;

        public Application(String[] argv) {
            init(argv);
        }

        public CompletionService<byte[]> getCompletionService() {
            return completionService;
        }

        public RandomByteService getRandomByteService() {
            return randomByteService;
        }

        public ServiceDispatch getServiceDispatch() {
            return serviceDispatch;
        }

        public ThreadPoolExecutor getThreadPool() {
            return threadPool;
        }

        public int getRequestsPerSecond() {
            return requestsPerSecond;
        }

        public int getRunTime() {
            return runTime;
        }

        public int getComputeSteps() {
            return computeSteps;
        }

        public long getThreadStackSize() {
            return threadStackSize;
        }

        public boolean isQuiet() {
            return quiet;
        }

        private void init(String[] argv) {

            ExecutionStrategy execStrategy = DEFAULT_EXECUTION_STRATEGY;
            WorkQueueStrategy workQueueStrategy = DEFAULT_WORK_QUEUE_STRATEGY;

            int workQueueCapacity = DEFAULT_WORK_QUEUE_CAPACITY;
            int corePoolSize = DEFAULT_CORE_POOL_SIZE;
            int maximumPoolSize = DEFAULT_MAXIMUM_POOL_SIZE;
            long keepAliveTime = DEFAULT_KEEP_ALIVE_TIME;

            requestsPerSecond = DEFAULT_REQUESTS_PER_SECOND;
            runTime = DEFAULT_RUN_TIME_IN_SECONDS;
            computeSteps = DEFAULT_COMPUTE_STEPS;
            threadStackSize = DEFAULT_THREAD_STACK_SIZE;

            quiet = DEFAULT_QUIET_OUTPUT;

            int c;
            String arg;

            LongOpt[] longOpts = { new LongOpt("execution-strategy", LongOpt.REQUIRED_ARGUMENT, null, 'e'),
                    new LongOpt("work-queue-strategy", LongOpt.REQUIRED_ARGUMENT, null, 'q'),
                    new LongOpt("work-queue-capacity", LongOpt.REQUIRED_ARGUMENT, null, 'c'),
                    new LongOpt("core-pool-size", LongOpt.REQUIRED_ARGUMENT, null, 'p'),
                    new LongOpt("maximum-pool-size", LongOpt.REQUIRED_ARGUMENT, null, 'm'),
                    new LongOpt("keep-alive-time", LongOpt.REQUIRED_ARGUMENT, null, 'k'),
                    new LongOpt("requests-per-second", LongOpt.REQUIRED_ARGUMENT, null, 'r'),
                    new LongOpt("run-time", LongOpt.REQUIRED_ARGUMENT, null, 't'),
                    new LongOpt("compute-steps", LongOpt.REQUIRED_ARGUMENT, null, 's'),
                    new LongOpt("thread-stack-size", LongOpt.REQUIRED_ARGUMENT, null, 1),
                    new LongOpt("quiet", LongOpt.NO_ARGUMENT, null, 2),
                    new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'h') };

            Getopt g = new Getopt("benchmark", argv, ":e:q:c:p:m:k:r:t:s:hW;", longOpts);

            while ((c = g.getopt()) != -1) {
                switch (c) {
                case 1:
                    arg = g.getOptarg();
                    threadStackSize = Long.valueOf(arg) * 1024;
                    break;

                case 2:
                    quiet = true;
                    break;

                case 'e':
                    arg = g.getOptarg();
                    execStrategy = ExecutionStrategy.valueOf(arg.toUpperCase());
                    if (execStrategy == null)
                        throw new IllegalArgumentException("Illegal execution strategy: " + arg);

                    break;

                case 'q':
                    arg = g.getOptarg();
                    workQueueStrategy = WorkQueueStrategy.valueOf(arg.toUpperCase());
                    if (workQueueStrategy == null)
                        throw new IllegalArgumentException("Illegal work queue strategy: " + arg);

                    break;

                case 'c':
                    arg = g.getOptarg();
                    workQueueCapacity = Integer.valueOf(arg);
                    break;

                case 'p':
                    arg = g.getOptarg();
                    corePoolSize = Integer.valueOf(arg);
                    break;

                case 'm':
                    arg = g.getOptarg();
                    maximumPoolSize = Integer.valueOf(arg);
                    break;

                case 'k':
                    arg = g.getOptarg();
                    keepAliveTime = Long.valueOf(arg);
                    break;

                case 'r':
                    arg = g.getOptarg();
                    requestsPerSecond = Integer.valueOf(arg);
                    break;

                case 't':
                    arg = g.getOptarg();
                    runTime = Integer.valueOf(arg);
                    break;

                case 's':
                    arg = g.getOptarg();
                    computeSteps = Integer.valueOf(arg);
                    break;

                case 'h':
                    printHelp(longOpts);
                    System.exit(0);
                    break;

                case ':':
                    System.err.println("Missing option argument");
                    break;

                case '?':
                    System.err.println("Ambiguous long option: " + g.getOptarg());
                    break;

                default:
                    throw new IllegalArgumentException("Unknown command line option: " + c);
                }
            }

            BlockingQueue<Runnable> workQueue = null;

            switch (workQueueStrategy) {
            case SYNCHRONOUS:
                workQueue = new SynchronousQueue<Runnable>();
                maximumPoolSize = Integer.MAX_VALUE;
                break;

            case BOUNDED:
                workQueue = new ArrayBlockingQueue<Runnable>(workQueueCapacity);
                break;

            case UNBOUNDED:
                workQueue = new LinkedBlockingQueue<Runnable>();
                break;
            }

            threadFactory = new ThreadFactory() {

                private static final String THREAD_NAME_FORMAT = "Benchmark-Pool-Thread-%d";
                private final AtomicInteger threadNumber = new AtomicInteger();

                @Override
                public Thread newThread(Runnable runnable) {
                    Thread thread = new Thread(null, runnable,
                            String.format(THREAD_NAME_FORMAT, threadNumber.incrementAndGet()), threadStackSize);

                    thread.setDaemon(true);

                    return thread;
                }
            };

            threadPool = createThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, workQueue,
                    threadFactory);
            completionService = createCompletionService(execStrategy, threadPool);
            randomByteService = new RandomByteService();

            switch (execStrategy) {
            case SYNCHRONOUS:
                serviceDispatch = new SynchronousDispatch(randomByteService);
                break;

            case CONTINUATION:
                serviceDispatch = new ContinuationDispatch(randomByteService, completionService);
                break;
            }

            System.out.println("Configuration:");
            System.out.println("  Execution Strategy: " + execStrategy);
            System.out.println("  Work Queue Strategy: " + workQueueStrategy);
            System.out.println("  Work Queue Capacity: " + workQueueCapacity);
            System.out.println("  Core Pool Size: " + corePoolSize);
            System.out.println("  Maximum Pool Size: " + maximumPoolSize);
            System.out.println("  Keep Alive Time (Seconds): " + keepAliveTime);
            System.out.println("  Requests Per Second: " + requestsPerSecond);
            System.out.println("  Run Time (Seconds): " + runTime);
            System.out.println("  Compute Steps: " + computeSteps);
            System.out.println("  Thread Stack Size (bytes): " + threadStackSize);

            System.out.println("JVM:");
            System.out.println("  Available Processors: " + Runtime.getRuntime().availableProcessors());
            System.out.println("  Free Memory: " + Runtime.getRuntime().freeMemory());
            System.out.println("  Max Memory: " + Runtime.getRuntime().maxMemory());
            System.out.println("  Total Memory: " + Runtime.getRuntime().totalMemory());
        }

        private CompletionService<byte[]> createCompletionService(ExecutionStrategy execStrategy,
                Executor threadPool) {

            CompletionService<byte[]> completionService = null;

            switch (execStrategy) {
            case SYNCHRONOUS:
                completionService = new ExecutorCompletionService<byte[]>(threadPool);
                break;

            case CONTINUATION:
                completionService = new ContinuationExecutorCompletionService<byte[]>(threadPool);
                break;
            }

            return completionService;
        }

        private ThreadPoolExecutor createThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
                long keepAliveTime, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {

            return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workQueue,
                    threadFactory);
        }

        private void printHelp(LongOpt[] longOpts) {
            for (LongOpt opt : longOpts) {
                System.out.printf("-%s --%s <%s>\n", (char) opt.getVal(), opt.getName(),
                        opt.getHasArg() == LongOpt.OPTIONAL_ARGUMENT ? "optional" : "required");
            }
        }
    }

    private static String toHexString(byte[] data) {
        StringBuilder sb = new StringBuilder();

        for (byte b : data)
            sb.append(String.format("%02X", b));

        return sb.toString();
    }

    public static void main(String[] argv) {
        Application app = new Application(argv);
        app.getThreadPool().prestartAllCoreThreads();

        int rejectedTasks = 0;
        long begTicks = System.currentTimeMillis();
        long endTicks;

        for (int seconds = 0; seconds < app.getRunTime(); seconds++) {
            try {
                for (int requests = 0; requests < app.getRequestsPerSecond(); requests++) {
                    Task task = new Task(app.getComputeSteps(), app.getServiceDispatch());

                    try {
                        app.getCompletionService().submit(task);
                    } catch (RejectedExecutionException e) {
                        rejectedTasks++;
                    }
                }

                Thread.sleep(1000L);

            } catch (InterruptedException ignored) {
            }
        }

        int remaining = (app.getRequestsPerSecond() * app.getRunTime()) - rejectedTasks;
        while (remaining > 0) {
            try {
                Future<byte[]> future = app.getCompletionService().take();
                if (!app.isQuiet())
                    System.out.println(toHexString(future.get()));
                remaining--;
            } catch (InterruptedException e) {
                // ignored
            } catch (ExecutionException e) {
                System.err.println(e.getMessage());
            }
        }

        endTicks = System.currentTimeMillis();
        System.out.printf("Finished processing in %d msec\n", endTicks - begTicks);

        System.out.println("Thread Pool Statistics:");
        System.out.println("  Active Count: " + app.getThreadPool().getActiveCount());
        System.out.println("  Completed Task Count: " + app.getThreadPool().getCompletedTaskCount());
        System.out.println("  Core Pool Size: " + app.getThreadPool().getCorePoolSize());
        System.out.println("  Largest Pool Size: " + app.getThreadPool().getLargestPoolSize());
        System.out.println("  Maximum Pool Size: " + app.getThreadPool().getMaximumPoolSize());
        System.out.println("  Pool Size: " + app.getThreadPool().getPoolSize());
        System.out.println("  Task Count: " + app.getThreadPool().getTaskCount());
        System.out.println("  Rejected Tasks: " + rejectedTasks);

        app.getRandomByteService().shutdown();
        app.getThreadPool().shutdown();
    }
}