2016-01-23 14 views
5

我正在寫一個小的Java應用程序來分析大量的圖像文件。目前,它通過平均圖像中每個像素的亮度並將其與文件夾中的其他圖像進行比較來找到文件夾中最亮的圖像。爲什麼啓動後我的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(); 
    } 
} 
+0

您可以使用jvisualvm等分析器運行您的應用程序,並使用其採樣器來測量哪種方法最耗時。另外,嘗試在單個線程中運行它並觀察任何差異。線程可能在某些操作上互相阻塞。 –

+2

你的類擴展了'Thread',並且你正在設置它的優先級,但是你通過'Executor'來運行它,它只把它看作是'Runnable',所以這一切都是徒勞的。如果你想影響優先級,你需要定義一個'ThreadFactory.',否則你的類可能只是實現Runnable。 – EJP

+0

你有沒有試過:'ImageIO.setUseCache(false);'? – user3707125

回答

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()); 
+0

有點類似於我的結果,parallelStream()方法在首先3000個左右的圖像(80-90%的CPU使用率),然後在CPU使用率下降到10-20%時經歷顯着的減速。 –

+0

@BrianVoter我會使用一個分析器來查看資源使用情況。這聽起來像你可能有內存泄漏。當進程減慢時,進程的總內存使用量是多少?當CPU變慢時,您是否看到重要的磁盤活動? –

3

我看到兩個原因,你可能會遇到CPU使用率惡化:

  • 你的任務是非常I/O密集型(讀取圖像 - ImageIO.read(f));
  • 您的線程訪問的同步方法存在線程爭用;

此外,圖像的大小可能會影響執行時間。

爲了有效地利用並行我建議你重新設計你的應用程序,並實現了兩個那種將提交給執行任務的:

  • 的首要任務(生產者)將I/O密集型和意志讀取圖像數據並將其排入內存處理;
  • 其他(消費者)會拉和分析圖像信息;

然後通過一些配置文件,您將能夠確定生產者和消費者之間的正確比例。

+0

感謝您的回答。每張圖片尺寸相同,供參考。我想知道 - 並行會加快文件讀取時間嗎?這個因素似乎取決於磁盤,我的直覺告訴我,使用並行性並不會更快,但顯然我不知道我在做什麼:P –

+0

@BrianVoter您的磁盤不是多線程的。 – EJP

+0

@EJP那麼這個事實會使這個答案變得糟糕,或者我誤解了它? –

0

我在這裏可以看到的問題是在您正在尋找的高性能併發模型中使用隊列。使用現代CPU設計時,使用隊列不是最佳選擇。隊列實現在頭部,尾部和大小變量上寫入爭用。由於消費者和生產者之間的速度差異,他們總是接近完全或接近空白,尤其是在高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

相關問題