为何增加线程数会导致Java 3D渲染程序性能下降?
多线程渲染性能下降问题排查与优化
我正在开发一款基于Java的软件3D渲染器,为提升显示区域更新速率,尝试将显示区分割为多个区域并为每个区域分配独立线程进行渲染。但结果却出现渲染速率下降的情况,这与预期不符。我了解创建超过机器处理器核心数的线程会影响程序性能,但我仅创建了少量线程,且我的机器是12核处理器。
现有代码实现
RenderThread父类的run()方法
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++; // 落后时立即更新 if (updates*interval < delta) { next = now; } // 按间隔更新 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(); } } }
RenderThread类
public class RenderThread extends TimedThread { public RenderThread(int rate, JavaForge ref) { super(rate, ref); } // 由父类run方法按固定频率调用 @Override protected void execute() { // 显示区域宽度(通过Canvas引用获取) int w = ref.getWidth(); // 显示区域高度 int h = ref.getHeight(); // 子线程列表 ArrayList<RenderSubThread> threads = new ArrayList<RenderSubThread>(); // 水平方向分区数 int partitionsWidth = 2; // 垂直方向分区数 int partitionsHeight = 1; // 每个分区的宽度 int pw = w / partitionsWidth; // 每个分区的高度 int ph = h / partitionsHeight; // 用于创建BufferedImage的颜色数据 int[] data = new int[w * h]; // 分区显示区域并为每个分区创建子线程 for (int y = 0; y < partitionsHeight; y++) { for (int x = 0; x < partitionsWidth; x++) { // 创建并启动子线程 threads.add(new RenderSubThread(x * pw, y * ph, pw, ph)); threads.getLast().start(); } } // 等待所有子线程完成并将数据合并到主数据数组 int index = 0; for (RenderSubThread thread : threads) { try { // 等待线程结束 thread.join(); // 拷贝数据 for (int i = 0; i < thread.data.length; i++) { data[index++] = thread.data[i]; } } catch (InterruptedException e) { e.printStackTrace(); } } // 创建显示图像 BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); // 将颜色数据写入图像 System.arraycopy(data, 0, ((DataBufferInt) img.getRaster().getDataBuffer()).getData(), 0, data.length); // 将图像渲染到屏幕 ref.render(img); } }
主类的render()方法(继承自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(); }
RenderSubThread类(处理分区渲染)
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() { // 初始化子线程数据数组 data = new int[h * w]; // 为每个像素分配随机颜色 for (int i = 0; i < data.length; i++) { data[i] = (int) (Math.random() * Integer.MAX_VALUE); } } }
问题根源分析
- 线程频繁创建销毁的开销:每次
execute()执行时都会新建并销毁RenderSubThread,线程的创建、启动和销毁本身就有不小的系统开销,哪怕是少量线程,频繁重复这个过程也会抵消并行渲染的收益。 - 冗余的数据拷贝操作:每个子线程生成独立的int数组,之后还要通过循环逐个拷贝到主数据数组,最后又要拷贝到BufferedImage的缓冲区,多次内存拷贝浪费了大量时间。
- 并行度过低:当前只分了2个渲染分区,对于12核CPU来说,大部分核心都处于闲置状态,完全没发挥多核处理器的优势。
优化方案
1. 使用线程池复用线程
提前创建固定大小的线程池(比如根据CPU核心数设置,比如Runtime.getRuntime().availableProcessors()),避免每次渲染都创建新线程,减少线程创建销毁的开销。
2. 直接操作共享缓冲区
预先分配好BufferedImage的int数组缓冲区,让每个子线程直接写入对应的区域,省去中间的数组拷贝步骤。例如:
// 在RenderThread的execute()中提前创建BufferedImage并获取其缓冲区 BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); int[] buffer = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); // 子线程直接写入共享缓冲区的对应位置 public class RenderSubThread implements Runnable { private int x; private int y; private int w; private int h; private int[] buffer; private int imgWidth; public RenderSubThread(int x, int y, int w, int h, int[] buffer, int imgWidth) { this.x = x; this.y = y; this.w = w; this.h = h; this.buffer = buffer; this.imgWidth = imgWidth; } @Override public void run() { for (int dy = 0; dy < h; dy++) { for (int dx = 0; dx < w; dx++) { int pixelIndex = (y + dy) * imgWidth + (x + dx); buffer[pixelIndex] = (int) (Math.random() * Integer.MAX_VALUE); } } } }
3. 调整分区粒度
增加分区数量,比如按CPU核心数来划分(比如分成12个分区),让每个核心都能参与渲染任务,提升并行效率。
4. 优化帧率控制逻辑
检查父类run()方法中的帧率控制,确保sleep的计算不会引入额外的延迟,比如可以考虑使用LockSupport.parkNanos()替代Thread.sleep(),精度更高。
内容的提问来源于stack exchange,提问作者Ethan
相关产品推荐
相关产品推荐

