消费者为什么会降低生产者的绩效

时间:2019-04-14 16:12:24

标签: java multithreading performance executorservice producer-consumer

我目前正在尝试通过实现生产者-消费者模式来提高软件的性能。在我的特定情况下,我有一个生产者按顺序创建行,并有多个使用者对给定的一批行执行某些任务。

我现在面临的问题是,当我测量生产者-消费者模式的性能时,我可以看到生产者的运行时间大大增加,而我不明白为什么会这样。

到目前为止,我主要描述了我的代码,并进行了微基准测试,但结果并没有导致我遇到实际的问题。

public class ProdCons {

    static class Row {
        String[] _cols;

        Row() {
            _cols = Stream.generate(() -> "Row-Entry").limit(5).toArray(String[]::new);
        }
    }

    static class Producer {

        private static final int N_ITER = 8000000;

        final ExecutorService _execService;

        final int _batchSize;

        final Function<Row[], Consumer> _f;

        Producer(final int batchSize, final int nThreads, Function<Row[], Consumer> f) throws InterruptedException {
            _execService = Executors.newFixedThreadPool(nThreads);
            _batchSize = batchSize;
            _f = f;
            // init all threads to exclude their generaration time
            startThreads();
        }

        private void startThreads() throws InterruptedException {
            List<Callable<Void>> l = Stream.generate(() -> new Callable<Void>() {

                @Override
                public Void call() throws Exception {
                    Thread.sleep(10);
                    return null;
                }

            }).limit(4).collect(Collectors.toList());
            _execService.invokeAll(l);
        }

        long run() throws InterruptedException {
            final long start = System.nanoTime();
            int idx = 0;
            Row[] batch = new Row[_batchSize];
            for (int i = 0; i < N_ITER; i++) {
                batch[idx++] = new Row();
                if (idx == _batchSize) {
                    _execService.submit(_f.apply(batch));
                    batch = new Row[_batchSize];
                    idx = 0;
                }
            }
            final long time = System.nanoTime() - start;
            _execService.shutdownNow();
            _execService.awaitTermination(100, TimeUnit.MILLISECONDS);
            return time;
        }
    }

    static abstract class Consumer implements Callable<String> {

        final Row[] _rowBatch;

        Consumer(final Row[] data) {
            _rowBatch = data;
        }

    }

    static class NoOpConsumer extends Consumer {

        NoOpConsumer(Row[] data) {
            super(data);
        }

        @Override
        public String call() throws Exception {
            return null;
        }
    }

    static class SomeConsumer extends Consumer {

        SomeConsumer(Row[] data) {
            super(data);
        }

        @Override
        public String call() throws Exception {
            String res = null;
            for (int i = 0; i < 1000; i++) {
                res = "";
                for (final Row r : _rowBatch) {
                    for (final String s : r._cols) {
                        res += s;
                    }
                }
            }

            return res;
        }

    }

    public static void main(String[] args) throws InterruptedException {
        final int nRuns = 10;
        long totTime = 0;
        for (int i = 0; i < nRuns; i++) {
            totTime += new Producer(100, 1, (data) -> new NoOpConsumer(data)).run();
        }
        System.out.println("Avg time with NoOpConsumer:\t" + (totTime / 1000000000d) / nRuns + "s");

        totTime = 0;
        for (int i = 0; i < nRuns; i++) {
            totTime += new Producer(100, 1, (data) -> new SomeConsumer(data)).run();
        }
        System.out.println("Avg time with SomeConsumer:\t" + (totTime / 1000000000d) / nRuns + "s");
    }

实际上,由于使用者在与生产者不同的线程中运行,所以我希望生产者的运行时间不受使用者工作量的影响。但是,运行程序我得到以下输出

#1线程,#100批处理大小

NoOpConsumer的平均时间:0.7507254368s

SomeConsumer的平均时间:1.5334749871s

请注意,时间测量仅测量生产时间,而不测量消费者时间,并且平均不要求提交任何作业。 〜0.6秒。

更令人惊讶的是,当我将线程数从1增加到4时,我得到以下结果(带有超线程的4核)。

#4个线程,#100批处理大小

NoOpConsumer的平均时间:0.7741189636s

SomeConsumer的平均时间:2.5561667638s

我做错什么了吗?我想念什么?目前,我不得不相信运行时间的差异是由于上下文切换或与我的系统相关的任何事情造成的。

1 个答案:

答案 0 :(得分:2)

线程之间没有完全隔离。

看起来您的SomeConsumer类分配了很多内存,这产生了垃圾回收工作,该工作在包括生产者线程在内的所有线程之间共享。

它还访问大量内存,这可能会使生产者使用的内存从L1或L2缓存中消失。访问实际内存比访问缓存要花费更长的时间,因此这也可能使您的生产者花费更长的时间。

还请注意,我实际上并没有验证您是否在正确地衡量制作人时间,并且很容易在此处犯错误。