javamultithreading

Why does increasing my thread count decrease the performance of my Java program?


I have a little software based 3d-renderer I am working on in Java. In an effort to increase the rate that my program can update the display area, I tried splitting the display area into sections and rendering each section in a separate thread. The result of this was a decrease in the render rate, which was unexpected. I understand that creating more threads than processors on my machine would in fact hinder the performance of my program, but I am only creating a few threads and I have a 12-core processor.

This is the method that calls the execute() method in my RenderThread, it is contained within the super-class of the RenderThread class

public final void run() {

        long now = System.nanoTime();
        long prev = now;
        long next = now;

        long delta = 0;

        int updates = 0;

        while (running) {
            prev = now;
            now = System.nanoTime();
            delta += now - prev;

            if (delta >= NANOS_PER_SECOND) {
                delta -= NANOS_PER_SECOND;
                System.out.println("updates:" + updates);
                updates = 0;
            }

            if (next <= now) {
                execute();
                updates++;
                //getting behind update immediately
                if (updates*interval < delta) {
                    next = now;
                } 
                //update at the next interval
                else {
                    next = now + interval;
                }
            }

            long sleep = Math.max(0, next - System.nanoTime());
            long millis = sleep / NANOS_PER_MILLI;

            int nanos = (int) (sleep - millis * NANOS_PER_MILLI);
            try {
                Thread.sleep(millis, nanos);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

    }

This is my RenderThread class.

public class RenderThread extends TimedThread {

    public RenderThread(int rate, JavaForge ref) {
        super(rate, ref);

    }

    // called by the super-class run method a set number of times per second
    @Override
    protected void execute() {

        //width of the display image (using Canvas ref)
        int w = ref.getWidth();
        //height of the display image
        int h = ref.getHeight();

        
        
        // the sub-thread list
        ArrayList<RenderSubThread> threads = new ArrayList<RenderSubThread>();
        
        //the number of partitions across the width of the display area
        int partitionsWidth = 2;
        //the number of partitions across the height of the display area
        int partitionsHeight = 1;

        //the width in pixels of each section
        int pw = w / partitionsWidth;
        //the height in pixels of each section
        int ph = h / partitionsHeight;

        //the color data used to create the BufferedImage
        int[] data = new int[w * h];

        // partition the display area of the program and create a sub-thread for each division
        for (int y = 0; y < partitionsHeight; y++) {
            for (int x = 0; x < partitionsWidth; x++) {
                //create and start the sub-thread
                threads.add(new RenderSubThread(x * pw, y * ph, pw, ph));
                threads.getLast().start();
            }
        }

        //wait for each sub-thread to finish and put its data into the main data array
        int index = 0;
        for (RenderSubThread thread : threads) {
            try {
                //wait to stop
                thread.join();
                //collect data
                for (int i = 0; i < thread.data.length; i++) {
                    data[index++] = thread.data[i];
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        //create display image
        BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        
        //put color data into display image
        System.arraycopy(data, 0, ((DataBufferInt) img.getRaster().getDataBuffer()).getData(), 0, data.length);
        
        //render image onto screen
        ref.render(img);
    }



}

Below is the method that gets called at the end of each execution() call. This method is contained within the main class which extends Canvas

public final void render(BufferedImage rendered) {

        BufferStrategy bs = getBufferStrategy();
        if (bs == null) {
            createBufferStrategy(3);
            bs = getBufferStrategy();
        }

        Graphics2D g = (Graphics2D) bs.getDrawGraphics();
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, getWidth(), getHeight());
        g.drawImage(rendered, 0, 0, null);

        bs.show();

    }

This is the class that handles the sections of the display area.

public class RenderSubThread extends Thread {

    int x;
    int y;
    int w;
    int h;
    int[] data;

    public RenderSubThread(int x, int y, int w, int h) {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
    }

    @Override
    public void run() {
        //initialize the sub-thread's data
        data = new int[h * w];

        //assign a random color for each pixel
        for (int i = 0; i < data.length; i++) {
            data[i] = (int) (Math.random() * Integer.MAX_VALUE);
        }

    }

}

Solution

  • Math.random()

    Note the Javadoc on Math.random():

    This method is properly synchronized to allow correct use by more than one thread. However, if many threads need to generate pseudorandom numbers at a great rate, it may reduce contention for each thread to have its own pseudorandom-number generator.

    In particular, only one thread can use Math.random() at a time, and all other threads will have to sit and wait for that thread to be done.

    ThreadLocalRandom#nextDouble

    One solution is to replace your Math.random() call with a call to ThreadLocalRandom#nextDouble.

    Replace this:

    data[i] = (int) (Math.random() * Integer.MAX_VALUE);
    

    ā€¦ with this:

    data[i] = (int) (ThreadLocalRandom.current().nextDouble( 0.0d , 1.0d ) * Integer.MAX_VALUE);