All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.sandia.cognition.algorithm.ParallelUtil Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
/*
 * File:                ParallelUtil.java
 * Authors:             Kevin R. Dixon
 * Company:             Sandia National Laboratories
 * Project:             Cognitive Foundry
 * 
 * Copyright Oct 10, 2008, Sandia Corporation.
 * Under the terms of Contract DE-AC04-94AL85000, there is a non-exclusive
 * license for use of this work by or on behalf of the U.S. Government. 
 * Export of this program may require a license from the United States
 * Government. See CopyrightHistory.txt for complete details.
 * 
 */

package gov.sandia.cognition.algorithm;

import gov.sandia.cognition.util.DefaultNamedValue;
import gov.sandia.cognition.util.NamedValue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Utility methods for creating parallel algorithms.
 *
 * @author Kevin R. Dixon
 * @since 3.0
 */
public class ParallelUtil
    extends Object
{

    /**
     * Indicates to the createThreadPool() method to estimate the "optimal"
     * number of threads for the computer currently executing the codes.
     */
    public static final int OPTIMAL_THREADS = -1;

    /**
     * Number of threads to use during a default call to createThreadPool()
     */
    private static int DEFAULT_NUM_THREADS = OPTIMAL_THREADS;

    /**
     * Protected constructor since this is a utility class.
     */
    protected ParallelUtil()
    {
        super();
    }

    /**
     * Gets the current default number of threads to use when called using the
     * default createThreadPool() method
     * @return
     * Number of threads to use during a default call to createThreadPool()
     */
    public static int getDefaultNumThreads()
    {
        return DEFAULT_NUM_THREADS;
    }

    /**
     * Sets the current default number of threads to use when calling the
     * default createThreadPool() method
     * @param defaultNumThreads
     * Number of threads to use during a default call to createThreadPool()
     */
    public static void setDefaultNumThreads(
        final int defaultNumThreads)
    {
        DEFAULT_NUM_THREADS = defaultNumThreads;
    }

    /**
     * Creates a thread pool with the "optimal" number of threads. The thread
     * pool creates daemon threads, so if all the non-daemon threads are
     * finished, then the application will not wait for the thread pool to be
     * cleaned up.
     *
     * @return
     * Thread pool with the "optimal" number of threads
     */
    public static ThreadPoolExecutor createThreadPool()
    {
        return createThreadPool(getDefaultNumThreads());
    }

    /**
     * Creates a thread pool with the given number of threads. The thread
     * pool creates daemon threads, so if all the non-daemon threads are
     * finished, then the application will not wait for the thread pool to be
     * cleaned up.
     * 
     * @param   numRequestedThreads
     *      Number of threads to create, or OPTIMAL_THREADS an "optimal" number
     *      of threads for the current computer.
     * @return
     *      Thread pool with the desired size that creates daemon threads.
     */
    public static ThreadPoolExecutor createThreadPool(
        int numRequestedThreads)
    {

        // Figure out how many cores or hyperthreads we have
        int numProcessors = Runtime.getRuntime().availableProcessors();

        int numThreads;

        // If the user wants the "optimal" thread count, then let's roll
        if (numRequestedThreads == OPTIMAL_THREADS)
        {
            // If we've got a two processors or less, then use all we got
            if (numProcessors <= 2)
            {
                numThreads = numProcessors;
            }
            // If we've got more than 2 processors, then leave one thread for
            // the GUI thread and the Operating System
            else
            {
                numThreads = numProcessors - 1;
            }
        }
        // If the user wants a specific number of treads, then let them
        // eat cake!!
        else
        {
            numThreads = numRequestedThreads;
        }

        // Make sure the user will get at least one thread
        if (numThreads < 1)
        {
            numThreads = 1;
        }

        return (ThreadPoolExecutor) Executors.newFixedThreadPool(numThreads,
            defaultDaemonThreadFactory());
    }

    /**
     * Gets the number of threads in a ParallelAlgorithm by querying the
     * thread pool 
     * @param algorithm
     * ParallelAlgorithm to consider
     * @return
     * Number of threads used by the ParallelAlgorithm's thread pool
     */
    public static int getNumThreads(
        final ParallelAlgorithm algorithm)
    {
        return (algorithm == null) ? 0 : getNumThreads(algorithm.getThreadPool());
    }

    /**
     * Gets the number of threads in a ThreadPoolExecutor
     * @param threadPool
     * ThreadPoolExecutor to consider
     * @return
     * Number of threads used by the thread pool
     */
    public static int getNumThreads(
        final ThreadPoolExecutor threadPool)
    {
        return (threadPool == null) ? 0 : threadPool.getMaximumPoolSize();
    }

    /**
     * Executes the given Callable tasks in parallel using a default thread
     * pool
     * @param  Type of results returned by the Callable tasks.
     * @param tasks
     * Callable tasks to be split across multiple cores
     * @return
     * Collection of results from the Callables
     * @throws java.lang.InterruptedException
     * If interrupted
     * @throws java.util.concurrent.ExecutionException
     * If the Callable task can't execute its method
     */
    public static  ArrayList executeInParallel(
        final Collection> tasks)
        throws InterruptedException, ExecutionException
    {
        return executeInParallel(tasks, ParallelUtil.createThreadPool());
    }

    /**
     * Executes the given Callable tasks in parallel using a given thread
     * pool
     * @param  Type of results returned by the Callable tasks.
     * @param tasks
     * Callable tasks to be split across multiple cores
     * @param threadPool 
     * Thread pool to use for the parallelization
     * @return
     * Collection of results from the Callables
     * @throws java.lang.InterruptedException
     * If interrupted
     * @throws java.util.concurrent.ExecutionException
     * If the Callable task can't execute its method
     */
    public static  ArrayList executeInParallel(
        Collection> tasks,
        ThreadPoolExecutor threadPool)
        throws InterruptedException, ExecutionException
    {

        Collection> futures = threadPool.invokeAll(tasks);
        ArrayList results =
            new ArrayList(futures.size());
        for (Future future : futures)
        {
            results.add(future.get());
        }

        return results;

    }

    /**
     * Executes the given Callable tasks in parallel using a given thread
     * pool
     * @param  Type of results returned by the Callable tasks.
     * @param tasks
     * Callable tasks to be split across multiple cores
     * @param algorithm
     * Uses the embedded thread pool in the ParallelAlgorithm
     * @return
     * Collection of results from the Callables
     * @throws java.lang.InterruptedException
     * If interrupted
     * @throws java.util.concurrent.ExecutionException
     * If the Callable task can't execute its method
     */
    public static  ArrayList executeInParallel(
        Collection> tasks,
        ParallelAlgorithm algorithm )
        throws InterruptedException, ExecutionException
    {
        return executeInParallel( tasks, algorithm.getThreadPool() );
    }

    /**
     * Executes the given Callable tasks sequentially in series.
     * @param  Type of results returned by the Callable tasks.
     * @param tasks
     * Callable tasks to execute sequentially
     * @return
     * Results from the sequential execution
     * @throws java.lang.Exception
     * If one of the Callables can't execute its method.
     */
    public static  ArrayList executeInSequence(
        Collection> tasks)
        throws Exception
    {
        ArrayList results = new ArrayList(tasks.size());
        for (Callable task : tasks)
        {
            results.add(task.call());
        }

        return results;

    }

    /**
     * Compares the times needed by running the tasks sequentially versus 
     * parallel.
     * @param  Result types of the Callable tasks
     * @param tasks
     * Callable tasks to execute in parallel or sequentially
     * @return
     * Named value with the name being a report of the time taken for parallel
     * execution, sequential execution, the parallel to sequential ratio, and
     * the number of threads used in the parallel execution.  The value
     * is the parallel to sequential ratio.  That is, 1.0 means it takes just
     * as long to execute in parallel as sequentially.  A value greater than
     * 1.0 means it takes longer in parallel than sequentially.  A value less
     * than 1.0 means it takes longer sequentially than in parallel.
     */
    public static  NamedValue compareTimes(
        Collection> tasks)
    {
        return compareTimes(tasks, createThreadPool());
    }

    /**
     * Compares the times needed by running the tasks sequentially versus 
     * parallel.
     * @param  Result types of the Callable tasks
     * @param tasks
     * Callable tasks to execute in parallel or sequentially
     * @param threadPool 
     * Thread pool to use for the parallelization
     * @return
     * Named value with the name being a report of the time taken for parallel
     * execution, sequential execution, the parallel to sequential ratio, and
     * the number of threads used in the parallel execution.  The value
     * is the parallel to sequential ratio.  That is, 1.0 means it takes just
     * as long to execute in parallel as sequentially.  A value greater than
     * 1.0 means it takes longer in parallel than sequentially.  A value less
     * than 1.0 means it takes longer sequentially than in parallel.
     */
    public static  NamedValue compareTimes(
        Collection> tasks,
        ThreadPoolExecutor threadPool)
    {
        try
        {
            // First, let's try things in series
            long seriesStart = System.currentTimeMillis();
            executeInSequence(tasks);
            long seriesStop = System.currentTimeMillis();

            long parallelStart = System.currentTimeMillis();
            executeInParallel(tasks, threadPool);
            long parallelStop = System.currentTimeMillis();

            double seriesTime = (seriesStop - seriesStart) / 1000.0;
            double parallelTime = (parallelStop - parallelStart) / 1000.0;
            double parallelToSeriesRatio = parallelTime / seriesTime;
            String name = "Series time = " + seriesTime + ", Parallel time = " +
                parallelTime + ", Parallel/Series ratio = " +
                parallelToSeriesRatio + " using " + ParallelUtil.getNumThreads(
                threadPool) + " threads.";
            return new DefaultNamedValue(name, parallelToSeriesRatio);
        }
        catch (Exception ex)
        {
            Logger.getLogger(ParallelUtil.class.getName()).log(Level.SEVERE,
                null, ex);
        }

        return null;

    }

    /**
     * Creates a version of the default thread factory from
     * {@code Executors.defaultThreadFactory()} that creates daemon threads.
     * Daemon threads mean that the application can exit even if the threads
     * are still alive.
     *
     * @return
     *      A default thread factory that creates daemon threads.
     */
    public static ThreadFactory defaultDaemonThreadFactory()
    {
        return new DaemonThreadFactory(Executors.defaultThreadFactory());
    }

    /**
     * Implements a thread factory for daemon threads. It just uses another
     * factory to actually create the threads and then calls
     * {@code setDaemon(true)}.
     */
    private static class DaemonThreadFactory
        implements ThreadFactory
    {

        /** The base factory to actually create the threads with. */
        protected ThreadFactory baseFactory;

        /**
         * Creates a new {@code DaemonThreadFactory}
         *
         * @param   baseFactory
         *      The base factory to actually create threads with.
         */
        public DaemonThreadFactory(
            final ThreadFactory baseFactory)
        {
            super();

            this.baseFactory = baseFactory;
        }

        public Thread newThread(
            final Runnable r)
        {
            // Create the thread.
            final Thread thread = this.baseFactory.newThread(r);

            // Set the thread to be a daemon, if we need to.
            if (!thread.isDaemon())
            {
                thread.setDaemon(true);
            }

            // Return the created thread.
            return thread;
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy