You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何增加线程数会导致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);
        }

    }

}

问题根源分析

  1. 线程频繁创建销毁的开销:每次execute()执行时都会新建并销毁RenderSubThread,线程的创建、启动和销毁本身就有不小的系统开销,哪怕是少量线程,频繁重复这个过程也会抵消并行渲染的收益。
  2. 冗余的数据拷贝操作:每个子线程生成独立的int数组,之后还要通过循环逐个拷贝到主数据数组,最后又要拷贝到BufferedImage的缓冲区,多次内存拷贝浪费了大量时间。
  3. 并行度过低:当前只分了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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 18:15:53