2

我正在用 Java 编写一个光线追踪程序,并使用 Runnable 接口实现了多线程。每个线程渲染 800 条垂直线的一部分。当使用两个线程时,每个线程将渲染 400 行。对于 8 个线程,每个线程 100 行,依此类推。

我的解决方案目前正在运行,但是当更多线程并行工作时,渲染时间不会减少。我的 CPU 有 8 个线程,在 8 个线程上渲染时使用率不是 100%。

class Multithread implements Runnable {
  Camera camera;
  CountDownLatch latch;
  ...

  //Constructor for thread
  Multithread(Scene s, Camera c, int thread, int threadcount, CountDownLatch cdl){
      camera = c;
      latch = cdl;
      ...
  }

  public void run(){
      try{
          ...
          //This is the render function
          camera.render(...);

          //When all threads unlatch, main class will write PNG
          latch.countDown();
      }
      catch (Exception e){System.out.println ("Exception is caught");}
  }
}
public class Camera {
    //The final pixel values are stored in the 2D-array
    ColorDbl[][] finalImage;
    
    Camera(int w){
        Width = w;
        finalImage = new ColorDbl[w][w]
    }

    //Start rendering
    void render(Scene S, int start, int end){

        //Create temporary, partial image
        ColorDbl[][] tempImage = new ColorDbl[Width][Width];

        Ray r;
        ColorDbl temp;
        //Render lines of pixels in the interval start-end
        for(int j = start; j < end; ++j){
            for(int i = 0; i < Width; ++i){
                r = new Ray(...);
                temp = r.CastRay(...);
                tempImage[i][j] = temp;
            }
        }

        //Copy rendered lines to final image
        for(int j=start; j<end; ++j){
            for(int i=0; i<Width; ++i){
                finalImage[i][j] = tempImage[i][j];
            }
        }
    }

    public static void main(String[] args) throws IOException{
        //Create camera and scene
        Camera camera = new Camera(800);
        Scene scene = new Scene();

        //Create threads
        int threadcount = 4;
        CountDownLatch latch = new CountDownLatch(threadcount);
        for (int thread=0; thread<threadcount; thread++){
            new Thread(new Multithread(scene, camera, thread, threadcount, latch)).start();
        }

        //Wait for threads to finish
        try{
          latch.await();
        }catch(InterruptedException e){System.out.println ("Exception");}

        //Write PNG
        c.write(...);
    }
}

当使用 2 个线程而不是 1 个线程时,我希望渲染速度几乎翻倍,但相反,它需要 50% 的时间。我不指望有人能解决我的问题,但在实现多线程时,我真的很感激一些指导。我会以错误的方式解决这个问题吗?

4

2 回答 2

0

在您发布的源代码中,我没有看到明显的瓶颈。当并行代码运行速度较慢时,最常见的解释要么是由于同步而产生的开销,要么是做额外的工作。

在同步方面,高拥塞会使并行代码运行非常缓慢。这可能意味着线程(或进程)正在争夺有限的资源(例如,等待锁),但它也可能更微妙,例如使用原子操作访问相同的内存,这可能会变得非常昂贵。在你的例子中,我没有看到类似的东西。唯一的同步操作似乎是最后的倒计时锁存器,这应该不重要。不平等的工作负载也会损害可伸缩性,但在您的示例中似乎不太可能。

做额外的工作可能是个问题。也许您在并行版本中复制的数据比在顺序版本中更多?这可以解释一些开销。另一个猜测是,在并行版本中,缓存位置受到了负面影响。请注意,缓存的影响很大(根据经验,当您的工作负载不再适合缓存时,内存访问可能会变慢 50-100 倍)。

如何找到你的瓶颈?通常,这称为分析。有专门的工具,例如,VisualVM是一个免费的 Java 工具,可以用作分析器。另一种更简单但通常非常有效的第一种方法是运行您的程序并进行一些随机线程转储。如果您有明显的瓶颈,您很可能会在堆栈跟踪中看到它。

该技术通常被称为穷人的探查器,但我发现它非常有效(有关更多详细信息,请参阅此答案)。此外,您还可以在生产中安全地应用它,因此当您必须优化无法在本地机器上运行的代码时,这是一个巧妙的技巧。

IDE(如 Eclipse 或 IntelliJ)支持进行线程转储,但如果您知道进程 ID,也可以直接从命令行触发它:

 kill -3 JAVA_PID

然后程序(或运行它的 JVM)将打印所有当前线程的当前堆栈跟踪。如果你重复几次,你应该知道你的程序大部分时间都花在了哪里。

您还可以将其与您的顺序版本进行比较。也许您注意到一些解释并行版本开销的模式。

我希望这对开始有所帮助。

于 2019-06-18T19:43:53.413 回答
0

我解决了这个问题,我终于明白为什么它不起作用了。

通过使用VisualVM进行一些调试,我注意到除了一个之外的所有线程一直被阻塞。我最初的解决方法是复制传递给每个线程的 Scene 对象。它解决了这个问题,但它并不优雅,对我来说没有意义。事实证明,真正的解决方案要简单得多。

我在场景类中使用 Vector<> 作为几何图形的容器。Vector<> 是一个同步容器,不允许多个线程同时访问它。相反,通过将场景中的所有对象放在 ArrayList<> 中,我可以得到更简洁的代码、更少的内存使用和更好的性能。

VisualVM对于找到阻塞至关重要,我感谢Philipp Claßen的建议,否则我永远不会解决这个问题。

于 2019-06-20T01:29:47.977 回答