ajeh
ajeh

Reputation: 2784

Is ExecutorService the most light-weight approach to parallel execution in terms of overhead per thread?

For an application that performs several identical calculation tasks repeatedly in a loop, would ExecutorService be the most appropriate solution to distributing the tasks among the CPUs in terms of overhead?

Below is the test application that I built using this answer https://stackoverflow.com/a/28632378/2721750 to find out that for 2 tasks running in parallel on 2 physical cores of Intel Core i5 2.5 GHz the overhead was approximately 0.1 millisecond per cycle.

Is there another solution that can help reduce the overhead?

The requirements are that the tasks need to receive a parameter and return a value from/to the main thread. They are the calls to a proprietory API that I cannot change.

package asyncthreadbenchmark;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;

public class AsyncThreadBenchmark {

    public static Double CalcResult(Integer p)
    {
        Long startTime = System.nanoTime();
        double d = 0;

        /* Simulating payload for the given number of milliseconds */
        while ((System.nanoTime() - startTime) / 1000000 < p)
        {
            d = Math.PI * Math.pow(Math.log((double)p), Math.E);
        }

        return d;
    }
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        class CalcTask implements Callable<Double> {
            Integer p;

            CalcTask(Integer parameter) {
                p = parameter;
            }

            @Override
            public Double call() {
                return CalcResult(p);
            }
        }

        /* 
        Using Intel Core i5 2.5 GHz dual core, execuction of 10,000 cycles 
        with 6 ms per task max resulted in 61024-61034 ms total.
        That is 0.1 ms overhead for 2 tasks per cycle.
        If the savings from running 2 tasks in parallel exceed 0.1 ms
        it makes sense to use Executor Service. Otherwise...
        */
        ExecutorService executor = Executors.newFixedThreadPool(3);

        try {
            Integer param1, param2, param3;
            param1 = 5;
            param2 = 6;
//            param3 = 4;

            Future aFuture, bFuture, cFuture;
            Double a, b, c;

            Long startTime = System.nanoTime(), elapsed;

            for (long i = 0; i< 10000; i++)
            {
                aFuture = executor.submit(new CalcTask(param1));
                bFuture = executor.submit(new CalcTask(param2));
//                cFuture = executor.submit(new CalcTask(param3));

                try {
                    a = (Double)aFuture.get();
                    b = (Double)bFuture.get();
//                    c = (Double)cFuture.get();
                } catch (InterruptedException | ExecutionException ex) {
//                    Logger.getLogger(AsyncThreadBenchmark.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            elapsed = (System.nanoTime() - startTime) / 1000000;
            System.out.println("Elapsed " + Long.toString(elapsed) + "ms");
        }
        finally {
            executor.shutdown();
        }
    }
}

Upvotes: 3

Views: 1109

Answers (1)

Brian Goetz
Brian Goetz

Reputation: 95356

It depends what you're trying to do.

ExecutorService is your go-to tool for executing independent, medium-granularity tasks that have a natural task granularity (e.g., servicing a user request.)

If instead, you want to execute fine-granularity cooperating tasks, such as those that arise from recursive decomposition of parallelizable problems, you probably want ForkJoinPool.

The parallel streams in Java 8 use ForkJoinPool for decomposing and executing stream operations. If you're not already an expert on parallelism, you are better off starting with parallel streams.

Upvotes: 1

Related Questions