2016-01-23 15 views
5

大量の画像ファイルを解析するための小さなJavaアプリケーションを作成しています。今のところ、画像内のすべてのピクセルの明るさを平均し、それをフォルダ内の他の画像と比較することによって、フォルダ内の最も明るい画像を見つけます。起動後にJavaプログラムのパフォーマンスが大幅に低下するのはなぜですか?

時々、スタートアップ直後に100+画像/秒のレートが得られますが、これはほとんど常に<20画像/秒に低下します。なぜそうはわかりません。 100+ images/secの場合、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などのプロファイラでアプリケーションを実行し、サンプラを使用して、どのメソッドが最も時間を要するかを測定できます。また、1つのスレッドで実行し、違いを観察してみてください。特定の操作でスレッドが互いにブロックしている可能性があります。 –

+2

あなたのクラスは 'Thread 'を拡張しています。優先度を設定していますが、' Executor'で実行しています。 'Runnable 'として扱うだけです。優先度に影響を与えたい場合は、 'ThreadFactory.'を定義する必要があります。それ以外の場合、クラスはRunnableを実装するだけです。 – EJP

+0

あなたは試みましたか: 'ImageIO.setUseCache(false);'? – user3707125

答えて

8

あなたはスレッドプールを使用して、独自のスレッドを作成し、両方の混同持っているように見えます。私はあなたの上で使用することをお勧めします。実際には、固定スレッドプールのみを使用することをお勧めします。

ほとんどの場合、スレッドは失われている例外が発生していますが、スレッドを強制終了するタスクは終了しています。

スレッドプールを作成することをお勧めします。独自のスレッドを作成しないでください。つまり、キューにはExecutorServiceがあります。各タスクについて、1つの画像につき1つずつプールに提出してください。エラーのチェックを行わない場合は、Throwableをすべてトラップしてログに記録してください。そうでなければRuntimeExcepionまたはErrorとなる可能性があります。 。

Java 8を使用している場合、より簡単な方法はparallelStream()を使用することです。これを使用して、画像を同時に分析し、作業を分割して結果を収集することなく結果を収集することができます。例えば

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

これまでの結果とまったく同じように、parallelStream (CPU使用率80〜90%)、CPU使用率が10〜20%に低下した場合は大幅に減速します。 –

+0

@BrianVoterプロファイラを使用してリソースの使用状況を調べます。メモリリークの可能性があります。処理速度が低下したときのプロセスの総メモリ使用量はいくらですか? CPUが減速したときに、重要なディスクアクティビティが表示されますか? –

3

私はあなたがCPU使用率の低下が発生する可能性があり、なぜ二つの理由を参照してください。

  • あなたのタスクです非常にI/O集中型(読み込み画像 - ImageIO.read(f))。
  • あなたのスレッドがアクセスするsynchronizedメソッドよりもスレッドの競合があります。

さらに、画像のサイズが実行時間に影響する場合があります。効率的に私はアプリを再設計し、実行者に提出されるだろうタスクの2種類実装することを示唆している並列処理を活用するために

  • 最初のタスク(生産者)がI/O集中型となりますでしょう画像データを読み込んでメモリ内処理のためにキューに入れる。
  • 他の(コンシューマー)はイメージ情報を引き出して分析します。

次に、プロファイリングによって、プロデューサとコンシューマの正確な比率を判断できます。

+0

あなたの答えをありがとう。すべての画像は参照用に同じサイズです。私は疑問に思っていました - 並列処理はファイルの読み取り時間を短縮しますか?この要因はディスクに依存しているように見えますが、私の直感からは、並列処理を使用する方がはるかに高速ではないことがわかりますが、明らかに私が何をしているのかわかりません:P –

+0

@BrianVoterディスクはマルチスレッドではありません。 – EJP

+0

@EJPので、この答えは貧弱なアプローチになりますか、それとも私はそれを誤解していますか? –

0

私が見ることができる問題は、探している高性能並行性モデルでのキューの使用です。現代のCPU設計では、キューを使用するのは最適ではありません。キュー実装では、head、tail、およびsize変数に書き込み競合が発生します。消費者と生産者の間のペースの違いにより、特に高いI/O状況で使用している場合、それらは常に完全に近いか空に近い状態にあります。その結果、競合が高レベルになります。さらに、Javaキューでは重大なガベージ・ソースがあります。

私が示唆しているのは、あなたのコードを設計する際に機械的な共感を適用することです。あなたが持つことができる最善の解決策の一つは、この同時実行の問題

その他の参考資料

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

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

を解決することを目的とした高性能スレッド間メッセージングライブラリであり、これは、 LMAX Disruptorの使い方です

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

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

関連する問題