1

I have a program that loads slowly, which I guess is due to the amount of image resources I have to load at the beginning. I thought multi-threading would help, but now I'm not so sure. Here is my automatic multi-threading method.

    private static Thread[] t;

    private static int currentThreads;

    public static void loadWithThreads(Object[] array, IntegerRunnable r) {

        final int threads =  Runtime.getRuntime().availableProcessors();
        t = new Thread[threads];

        for (int i = 0; i < threads; i ++) {
            t[i] = new Thread("HMediaConverter") {

                final int id = currentThreads;

                int items = (array.length / threads) * currentThreads;


                @Override
                public void run() {

                    super.run();

                    for (int i = items; i < (items + (array.length / threads)); i ++) {
                        r.run(i);
                    }

                    //Recycle this thread so it can be used for another time.
                    try {
                        t[id].join();
                        lock.notifyAll();
                        currentThreads --;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }


            };
            t[i].setPriority(Thread.MAX_PRIORITY);
            t[i].start();
            currentThreads ++;
        }
    }

And here is my image loading code:

public static ImageIcon loadImageIcon(String path) {
    return new ImageIcon(ImageIO.read(Tools.class.getClassLoader().getResource(path));
}

Surely there is a way to speed things up? I'm running this on a perfectly good Intel i5, it shouldn't be this slow, so it must be my code.

Jeffrey Bosboom
  • 13,313
  • 16
  • 79
  • 92
  • 2
    Use an `ExecutorService` instead, may not resolve the issue, but it would be a lot cleaner then what you're doing now – MadProgrammer Dec 15 '15 at 03:23
  • 1
    Also, once a `Thread`'s `run` method exists, it can't be re-started – MadProgrammer Dec 15 '15 at 03:24
  • @MadProgrammer May I ask what you were referring to when you said the `run()` method can't be restarted? I don't quite understand what you're getting at. And Thank you for the quick feedback; I appreciate it. – PragmaticObject Dec 15 '15 at 03:26
  • `t[id].join();` will deadlock, as the current thread can't exit until `join` returns, but `join` won't return until the `run` method has exited – MadProgrammer Dec 15 '15 at 03:28
  • *"May I ask what you were referring to when you said the run() method can't be restarted?"* - Thread's are not re-entrant, that is, once the `run` method exists, you can't re-use the instance of the `Thread`, so `//Recycle this thread so it can be used for another time.` isn't going to work, the `Thread` will not be restartable. It's also impossible to comment on whether is will work (or is a good idea) without some idea of what you are trying to load, the number of resources and there size – MadProgrammer Dec 15 '15 at 03:31
  • @MadProgrammer Ahhhh ok, I was going to use `t[id].yield();` but wasn't sure if that was the way to go, even though they are different. – PragmaticObject Dec 15 '15 at 03:32
  • @MadProgrammer I also must include that my variable names and comments will not be very accurate in these methods because I have never attempted Multi-threading before. I appreciate the critisism. – PragmaticObject Dec 15 '15 at 03:34
  • So, using 113 images (159.14mb), without any threading, it took ~15s. Using a modified version of your code, it took ~11s – MadProgrammer Dec 15 '15 at 03:43
  • @MadProgrammer Is the modified version of my code basically the same as my code? Or did it improve performance? – PragmaticObject Dec 15 '15 at 03:51
  • The modified version was modified to remove the deadlock and use of `IntegerRunnable`, used `ImageIO.read` instead – MadProgrammer Dec 15 '15 at 03:52
  • @MadProgrammer So, I'm assuming this is the fastest option I'm going to get? There's nothing we can do to speed up this process? – PragmaticObject Dec 15 '15 at 03:54
  • I played around with `ExecutorService` and got it down to ~7 seconds – MadProgrammer Dec 15 '15 at 03:57

3 Answers3

4

Loading 113 images of a total of 159.14mb with...

public static void loadWithoutThreads(File[] array) {
    for (File file : array) {
        try {
            ImageIO.read(file);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

Took ~15s

With...

public static void loadWithThreads(File[] array) {

    final int threads = Runtime.getRuntime().availableProcessors();
    t = new Thread[threads];

    CountDownLatch latch = new CountDownLatch(threads);

    for (int i = 0; i < threads; i++) {
        t[i] = new Thread("HMediaConverter") {
            final int id = currentThreads;

            int items = (array.length / threads) * currentThreads;

            @Override
            public void run() {
                try {
                    System.out.println("Starting " + id);

                    for (int i = items; i < (items + (array.length / threads)); i++) {
                        try {
                            System.out.println(i + ": " + array[i]);
                            ImageIO.read(array[i]);
                        } catch (IOException ex) {
                            ex.printStackTrace();
                        }
                    }
                } finally {
                    latch.countDown();
                }

            }

        };
        t[i].setPriority(Thread.MAX_PRIORITY);
        System.out.println("Start " + i);
        t[i].start();
        currentThreads++;
    }

    try {
        latch.await();
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
}

took ~11s

With...

public static void loadWithExecutor(File[] images) {
    ExecutorService service = Executors.newFixedThreadPool(2);
    List<ImageLoadingTask> tasks = new ArrayList<>(images.length);
    for (File file : images) {
        tasks.add(new ImageLoadingTask(file));
    }
    try {
        List<Future<BufferedImage>> results = service.invokeAll(tasks);
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
    service.shutdown();
}

public static class ImageLoadingTask implements Callable<BufferedImage> {

    private File file;

    public ImageLoadingTask(File file) {
        this.file = file;
    }

    @Override
    public BufferedImage call() throws Exception {
        return ImageIO.read(file);
    }

}

Took ~7s

The ExecutorService is more efficient because when one thread is processing a larger file, the other can be processing a number of small files. This is achieved by pooling the threads that aren't doing any work until they are needed, allowing a thread to perform a lot of short work, while the other thread(s) are also busy. You don't need to wait as long

Have a look at Executors for more details

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
1

The following is a re-write that should work that is close to what the op wrote. A re-write into A fixed-size thread pool would probably be better.

//import java.util.concurrent.atomic.AtomicInteger;

private static Thread[] t;

    private static AtomicInteger completedLoads = new AtomicInteger(0);

    public static void loadWithThreads(Object[] array, IntegerRunnable r) {

        final int threads =  Runtime.getRuntime().availableProcessors();
        t = new Thread[threads];
        completedLoads = new AtomicInteger(0);
        int targetLoads = array.length;
        int itemsPerThread = (array.length / threads);

        for (int i = 0; i < threads; i ++) {
            t[i] = new Thread("HMediaConverter" + i) {

                int startItem = itemsPerThread * i;

                @Override
                public void run() {

                    super.run();

                    for (int i = startItem; i < startItem + itemsPerThread; i ++) {
                        try {
                            r.run(i);
                         }
                         finally {
                                 completedLoads.incrementAndGet();
                         }
                    }
                }
            };
            t[i].setPriority(Thread.MAX_PRIORITY);
            t[i].start();
        }

        // Wait for the images to load    
        while (completedLoads.get() < targetLoads)
        {
                try {
                        Thread.sleep(100);
                }
                catch (InterruptedException ie) {
                        // ignore
                }
        }
    }
hack_on
  • 2,532
  • 4
  • 26
  • 30
  • Do some quick research into `CountDownLatch`, your `while (completedLoads.get() < targetLoads)` is redundent and wasteful – MadProgrammer Dec 15 '15 at 03:58
0

Isolate which part does the slowing down - e.g by running System.currentTimeMillis() btween major segmnst then show us where is the biggest time - or show us all the program.

Threads handling as noted is questionable and you shouldn't use methods such as join etc out of the box unless you have seen it sometwhere provably working.

So post times and we'll take it from there - it could be the images it could be the threads

gpasch
  • 2,672
  • 3
  • 10
  • 12
  • Sounds good to me; however I will say, that adding this threading improved the speed by a few hundred ms, but not as much as I would like it to. – PragmaticObject Dec 15 '15 at 03:58