为什么启动后我的Java程序性能会显着下降?

时间:2016-01-23 19:32:16

标签: java multithreading performance concurrency

我正在编写一个小型Java应用程序来分析大量的图像文件。目前,它通过平均图像中每个像素的亮度并将其与文件夹中的其他图像进行比较,找到文件夹中最亮的图像。

有时,我在启动后立即获得100+图像/秒的速率,但这几乎总是下降到< 20张图像/秒,我不知道为什么。当它处于100+图像/秒时,CPU使用率为100%,但随后降至20%左右,这似乎太低了。

这是主要课程:

public class ImageAnalysis {

    public static final ConcurrentLinkedQueue<File> queue = new ConcurrentLinkedQueue<>();
    private static final ConcurrentLinkedQueue<ImageResult> results = new ConcurrentLinkedQueue<>();
    private static int size;
    private static AtomicInteger running = new AtomicInteger();
    private static AtomicInteger completed = new AtomicInteger();
    private static long lastPrint = 0;
    private static int completedAtLastPrint;

    public static void main(String[] args){
        File rio = new File(IO.CAPTURES_DIRECTORY.getAbsolutePath() + File.separator + "Rio de Janeiro");

        String month = "12";

        Collections.addAll(queue, rio.listFiles((dir, name) -> {
            return (name.substring(0, 2).equals(month));
        }));

        size = queue.size();

        ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1);

        for (int i = 0; i < 8; i++){
            AnalysisThread t = new AnalysisThread();
            t.setPriority(Thread.MAX_PRIORITY);
            executor.execute(t);
            running.incrementAndGet();
        }
    }

    public synchronized static void finished(){
        if (running.decrementAndGet() <= 0){

            ImageResult max = new ImageResult(null, 0);

            for (ImageResult r : results){
                if (r.averageBrightness > max.averageBrightness){
                    max = r;
                }
            }

            System.out.println("Max Red: " + max.averageBrightness + " File: " + max.file.getAbsolutePath());
        }
    }

    public synchronized static void finishedImage(ImageResult result){
        results.add(result);
        int c = completed.incrementAndGet();

        if (System.currentTimeMillis() - lastPrint > 10000){
            System.out.println("Completed: " + c + " / " + size + " = " + ((double) c / (double) size) * 100 + "%");
            System.out.println("Rate: " + ((double) c - (double) completedAtLastPrint) / 10D + " images / sec");
            completedAtLastPrint = c;

            lastPrint = System.currentTimeMillis();
        }
    }

}

线程类:

public class AnalysisThread extends Thread {

    @Override
    public void run() {
        while(!ImageAnalysis.queue.isEmpty()) {
            File f = ImageAnalysis.queue.poll();

            BufferedImage image;
            try {
                image = ImageIO.read(f);

                double color = 0;
                for (int x = 0; x < image.getWidth(); x++) {
                    for (int y = 0; y < image.getHeight(); y++) {
                        //Color c = new Color(image.getRGB(x, y));
                        color += image.getRGB(x,y);
                    }
                }

                color /= (image.getWidth() * image.getHeight());

                ImageAnalysis.finishedImage((new ImageResult(f, color)));

            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        ImageAnalysis.finished();
    }
}

3 个答案:

答案 0 :(得分:8)

您似乎混淆了使用线程池和创建自己的线程。我建议你使用或其他。实际上我建议你只使用固定的线程池

最有可能发生的事情是你的线程正在获得一个异常,这个异常正在丢失但却杀死了杀死线程的任务。

我建议你只是线程池,不要尝试创建自己的线程,或排队,因为这是ExecutorService为你做的。对于每个任务,将其提交到池中,每个图像一个,如果您不打算检查任何任务的错误,我建议您捕获所有Throwable并记录它们,否则您可以获得RuntimeExcepionError并且不知道发生了这种情况。

如果你有Java 8,更简单的方法是使用parallelStream()。您可以使用它来同时分析图像并收集结果,而无需分割工作并收集结果。 e.g

List<ImageResults> results = Stream.of(rio.listFiles())
                                   .parallel()
                                   .filter(f -> checkFile(f))
                                   .map(f -> getResultsFor(f))
                                   .list(Collectors.toList());

答案 1 :(得分:3)

我看到了两个可能导致CPU使用率下降的原因:

  • 您的任务非常耗费I / O(阅读图片 - ImageIO.read(f));
  • 线程访问的同步方法存在线程争用;

此外,图像的大小可能会影响执行时间。

为了有效地利用并行性,我建议您重新设计应用程序并实现两种将提交给执行程序的任务:

  • 第一个任务(生产者)将是I / O密集型的,并将读取图像数据并将其排队以进行内存处理;
  • 其他(消费者)将提取和分析图像信息;

然后通过一些分析,您将能够确定生产者和消费者之间的正确比率。

答案 2 :(得分:0)

我在这里看到的问题是在您正在寻找的高性能并发模型中使用队列。使用现代CPU设计时,使用队列不是最佳选择。队列实现对head,tail和size变量进行写入争用。由于消费者和生产者之间的节奏差异,特别是在高I / O情况下使用时,它们要么总是接近满或接近空。这导致高水平的争用。此外,在Java队列中,垃圾是重要的垃圾来源。

我建议在设计代码时应用Mechanical Sympathy。您可以使用的最佳解决方案之一是LMAX Disruptor的使用,它是一个高性能的线程间消息传递库,旨在解决此并发问题

其他参考资料

http://lmax-exchange.github.io/disruptor/files/Disruptor-1.0.pdf

http://martinfowler.com/articles/lmax.html

https://dzone.com/articles/mechanical-sympathy

http://www.infoq.com/presentations/mechanical-sympathy