2016-01-23 5 views
5

Sto scrivendo una piccola applicazione Java per analizzare un gran numero di file di immagine. Per ora, trova l'immagine più luminosa in una cartella calcolando la media di luminosità di ogni pixel nell'immagine e confrontandola con le altre immagini nella cartella.Perché le prestazioni del mio programma Java diminuiscono significativamente dopo l'avvio?

A volte, ottengo un tasso di 100+ immagini/secondo subito dopo l'avvio, ma questo scende quasi sempre a < 20 immagini al secondo, e non sono sicuro del perché. Quando è a 100+ immagini al secondo, l'utilizzo della CPU è al 100%, ma poi scende a circa il 20%, il che sembra troppo basso.

Ecco la classe principale:

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(); 
     } 
    } 

} 

E la classe thread:

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

Puoi eseguire la tua app con un profiler come jvisualvm e usare il suo campionatore per misurare quale metodo impiega più tempo. Inoltre, provare a eseguirlo in un singolo thread e osservare eventuali differenze. I thread potrebbero bloccarsi a vicenda su determinate operazioni. –

+2

La tua classe estende 'Thread,' e stai impostando la sua priorità, ma la stai eseguendo tramite un 'Executor', che lo tratta come un 'Runnable', quindi tutto questo è inutile. Se vuoi influenzare la priorità devi definire un 'ThreadFactory'. Altrimenti la tua classe potrebbe anche implementare Runnable. – EJP

+0

Hai provato: 'ImageIO.setUseCache (false);'? – user3707125

risposta

8

Si sembrano avere un mescolato sia utilizzando un pool di thread e la creazione di discussioni di tua scelta. Ti suggerisco di usare su o l'altro. In realtà ti suggerisco di utilizzare solo il pool di thread fisso

Molto probabilmente ciò che sta accadendo è che i tuoi thread ottengono un'eccezione che va persa ma che uccide l'attività che uccide il thread.

Ti suggerisco solo il pool di thread, non tentare di creare i tuoi thread o la coda in quanto questo è che il ExecutorService fa per te. Per ogni attività, inviala al pool, una per immagine e se non hai intenzione di controllare l'errore di qualsiasi attività, ti suggerisco di intercettare tutti gli Throwable e di registrarli altrimenti potresti ottenere un RuntimeExcepion o Error e non avere idea di ciò che è successo .

Se si dispone di Java 8, un approccio più semplice sarebbe utilizzare parallelStream(). È possibile utilizzare questo per analizzare le immagini contemporaneamente e raccogliere i risultati senza dover dividere il lavoro e raccogliere i risultati. per esempio

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

In modo simile ai risultati che ho avuto, l'approccio parallelStream() procede molto rapidamente sul prime 3000 immagini (80-90% di utilizzo della CPU), quindi si verifica un rallentamento significativo in cui l'utilizzo della CPU scende al 10-20%. –

+0

@BrianVoter Vorrei utilizzare un profiler per esaminare l'utilizzo delle risorse. Sembra che tu possa avere una perdita di memoria. Qual è l'utilizzo totale della memoria del processo quando rallenta? Vedete un'attività significativa del disco quando la CPU rallenta? –

3

Vedo due motivi per cui si può verificare l'utilizzo della CPU peggioramento:

  • le attività sono molto I/O intensive (lettura immagini - ImageIO.read(f));
  • c'è contesa del thread sul metodo sincronizzato a cui i thread accedono;

Ulteriori dimensioni delle immagini possono influenzare i tempi di esecuzione.

Per sfruttare il parallelismo in modo efficiente Vorrei suggerire che si ridisegnare il vostro app e implementare due tipi di compiti che sarebbero sottoposte al esecutore:

  • primi compiti (produttori) sarebbe di I/O intensive e volontà leggere i dati dell'immagine e metterli in coda per l'elaborazione in memoria;
  • l'altro (consumatori) tirerà e analizzerà le informazioni dell'immagine;

Quindi con alcuni profili sarà possibile determinare il rapporto corretto tra produttori e consumatori.

+0

Grazie per la risposta. Ogni immagine ha le stesse dimensioni, per riferimento. Mi stavo chiedendo: il parallelismo accelererebbe i tempi di lettura dei file? Questo fattore sembra dipendere dal disco e la mia intuizione mi dice che non sarebbe molto più veloce usare il parallelismo, ma ovviamente non so cosa sto facendo: P –

+0

@BrianVoter Il tuo disco non è multi-thread. – EJP

+0

@ EJP, quindi questo renderebbe questa risposta un approccio inadeguato, o lo sto fraintendendo? –

0

Il problema che ho potuto vedere qui è l'uso di code nel modello di concorrenza ad alte prestazioni che si sta cercando.L'utilizzo di una coda non è ottimale durante l'utilizzo con un moderno design della CPU. Le implementazioni delle code hanno conflitti di scrittura sulle variabili head, tail e size. Sono sempre vicini al pieno o quasi vuoti a causa delle differenze di ritmo tra consumatori e produttori, specialmente durante l'utilizzo in una situazione di I/O elevata. Ciò si traduce in alti livelli di contesa. Inoltre, nelle code Java è una significativa fonte di spazzatura.

Quello che suggerisco è di applicare Sympathy Mechanical durante la progettazione del codice. Uno dei la soluzione migliore che si può avere è l'utilizzo di LMAX Disruptor, che è una libreria di messaggistica inter-thread ad alte prestazioni, che mira a risolvere questo problema di concorrenza

Ulteriori riferimenti

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

Problemi correlati