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