Why did my Java program performance drop significantly after launch?

I am writing a small Java application to analyze a large number of image files. At the moment, he finds the brightest image in the folder, averaging the brightness of each pixel in the image and comparing it with other images in the folder.

Sometimes I get a speed of 100+ images per second right after launch, but it almost always drops to <20 images per second, and I'm not sure why. When it is 100+ images / sec, CPU usage is 100%, but then it drops to about 20%, which seems too low.

Here is the main class:

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

And stream class:

 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(); } } 
+5
source share
3 answers

It looks like you have mixed work using a thread pool and creating your own threads. I suggest you use one or the other. In fact, I suggest you use only a fixed thread pool

Most likely what happens is your threads get an exception that gets lost but kills the task that kills the thread.

I suggest you just a thread pool, do not try to create your own threads or queues, as this means that ExecutorService for you. For each task, send it to the pool, one per image, and if you are not going to check the error of any task, I suggest that you block all Throwable and write them down otherwise, you could get a RuntimeExcepion or Error and I have no idea what it is occurred.

If you have Java 8, a simpler approach is to use parallelStream (). You can use this to simultaneously analyze images and collect the results without having to split the work and collect the results. eg

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

I see two reasons why a decrease in CPU usage may occur:

  • your tasks are very intensive input / output (reading images - ImageIO.read(f) );
  • there is a thread conflict by the synchronized method that your threads are accessing;

In addition, image sizes can affect runtime.

To use parallelism effectively, I suggest you redesign your application and implement two types of tasks that will be sent to the executor:

  • the first tasks (manufacturers) will be intensive I / O and will read image data and put them in a queue for processing in memory;
  • others (consumers) will extract and analyze image information;

Then, with some profiling, you can determine the right relationship between producers and consumers.

+3
source

The problem I see here is the use of queues in the high-performance concurrency model you are looking for. Queue utilization is not optimal when used with a modern CPU design. Queue implementations have write access to the head, tail, and size variables. They are either always close to full, or close to empty due to differences in pace between consumers and manufacturers, especially when used in high I / O situations. This leads to high levels of competition. In addition, in Java, queues are a significant source of garbage.

I suggest applying mechanical liking when developing code. One of the best solutions you can get is to use LMAX Disruptor , which is a high-performance cross-thread message library that aims to solve this concurrency problem

Sitelinks

http://lmax-exchange.imtqy.com/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

0
source

Source: https://habr.com/ru/post/1241323/


All Articles