ExecutorService's surprising performance break-even point — rules of thumb?

后端 未结 9 1991
臣服心动
臣服心动 2020-12-08 03:10

I\'m trying to figure out how to correctly use Java\'s Executors. I realize submitting tasks to an ExecutorService has its own overhead. However, I\'m surpris

9条回答
  •  自闭症患者
    2020-12-08 03:48

    The 'overhead' you mention is nothing to do with ExecutorService, it is caused by multiple threads synchronizing on Math.random, creating lock contention.

    So yes, you are missing something (and the 'correct' answer below is not actually correct).

    Here is some Java 8 code to demonstrate 8 threads running a simple function in which there is no lock contention:

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    import java.util.function.DoubleFunction;
    
    import com.google.common.base.Stopwatch;
    
    public class ExecServicePerformance {
    
        private static final int repetitions = 120;
        private static int totalOperations = 250000;
        private static final int cpus = 8;
        private static final List batches = batches(cpus);
    
        private static DoubleFunction performanceFunc = (double i) -> {return Math.sin(i * 100000 / Math.PI); };
    
        public static void main( String[] args ) throws InterruptedException {
    
            printExecutionTime("Synchronous", ExecServicePerformance::synchronous);
            printExecutionTime("Synchronous batches", ExecServicePerformance::synchronousBatches);
            printExecutionTime("Thread per batch", ExecServicePerformance::asynchronousBatches);
            printExecutionTime("Executor pool", ExecServicePerformance::executorPool);
    
        }
    
        private static void printExecutionTime(String msg, Runnable f) throws InterruptedException {
            long time = 0;
            for (int i = 0; i < repetitions; i++) {
                Stopwatch stopwatch = Stopwatch.createStarted();
                f.run(); //remember, this is a single-threaded synchronous execution since there is no explicit new thread
                time += stopwatch.elapsed(TimeUnit.MILLISECONDS);
            }
            System.out.println(msg + " exec time: " + time);
        }    
    
        private static void synchronous() {
            for ( int i = 0; i < totalOperations; i++ ) {
                performanceFunc.apply(i);
            }
        }
    
        private static void synchronousBatches() {      
            for ( Batch batch : batches) {
                batch.synchronously();
            }
        }
    
        private static void asynchronousBatches() {
    
            CountDownLatch cb = new CountDownLatch(cpus);
    
            for ( Batch batch : batches) {
                Runnable r = () ->  { batch.synchronously(); cb.countDown(); };
                Thread t = new Thread(r);
                t.start();
            }
    
            try {
                cb.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }        
        }
    
        private static void executorPool() {
    
            final ExecutorService es = Executors.newFixedThreadPool(cpus);
    
            for ( Batch batch : batches ) {
                Runnable r = () ->  { batch.synchronously(); };
                es.submit(r);
            }
    
            es.shutdown();
    
            try {
                es.awaitTermination( 10, TimeUnit.SECONDS );
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } 
    
        }
    
        private static List batches(final int cpus) {
            List list = new ArrayList();
            for ( int i = 0; i < cpus; i++ ) {
                list.add( new Batch( totalOperations / cpus ) );
            }
            System.out.println("Batches: " + list.size());
            return list;
        }
    
        private static class Batch {
    
            private final int operationsInBatch;
    
            public Batch( final int ops ) {
                this.operationsInBatch = ops;
            }
    
            public void synchronously() {
                for ( int i = 0; i < operationsInBatch; i++ ) {
                    performanceFunc.apply(i);
                }
            }
        }
    
    
    }
    

    Result timings for 120 tests of 25k operations (ms):

    • Synchronous exec time: 9956
    • Synchronous batches exec time: 9900
    • Thread per batch exec time: 2176
    • Executor pool exec time: 1922

    Winner: Executor Service.

提交回复
热议问题