【问题标题】:Incomplete multi-threading RayTracer taking twice as much time as expected不完整的多线程 RayTracer 花费的时间是预期的两倍
【发布时间】:2018-10-12 00:19:10
【问题描述】:

我正在制作一个 MT Ray-Tracer 多线程,正如标题所说,它的执行时间是单线程版本的两倍。显然目的是将渲染时间减少一半,但我现在所做的只是发送光线追踪方法运行两次,每个线程一次,基本上执行两次相同的渲染。尽管如此,由于线程可以并行运行,因此执行时间不会显着增加。但是大约是翻倍。

这必须与我的多线程设置有关。我认为这与我将它们创建为可连接的事实有关。因此,我将解释我在做什么,并放上相关代码,看看是否有人可以确认是否是问题所在。

我创建了两个线程并将它们设置为可连接的。创建一个 RayTracer,它分配足够的内存来存储图像像素(这在构造函数中完成)。运行两次迭代循环以发送每个线程的相关信息,例如线程 ID 和 Raytracer 实例的地址。

然后pthread_create调用run_thread,其目的是调用ray_tracer:draw方法完成工作。关于draw方法,我有一个

 pthread_exit (NULL); 

作为它上面的最后一件事(上面唯一的 MT 东西)。然后再做一个循环来加入线程。最后,我开始在一个小循环中写入文件。最后关闭文件,删除draw方法中用于存储图像的数组相关的指针。

我现在可能不需要使用加入,因为我不是在做一个“真正的”多线程光线追踪器,只是渲染它两次,但是一旦我开始在图像像素之间交替(即 thread0 -> 渲染 pixel0 - thread0 -> 存储 pixel0, thread1 -> 渲染 pixel1 - thread1 -> 存储 pixel1, thread0 -> 渲染 pixel2 - thread0 -> 存储 pixel2, , thread1 -> 渲染 pixel3 - thread1 -> 存储 pixel3 等...)我认为我需要它以便能够在文件上以正确的顺序写入像素。

正确吗?我真的需要在这里使用我的方法(或任何其他方法吗?)。如果我这样做,我怎样才能发送线程并发运行,而不是等待另一个完成?问题是否与加入完全无关?

pthread_t threads [2];
thread_data td_array [2];
pthread_attr_t attr;
void *status;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
TGAManager tgaManager ("z.tga",true);
if (tgaManager.isFileOpen()) {
   tgaManager.writeHeadersData (image);
   RayTracer rt (image.getHeight() * image.getWidth());
   int rc;
   for (int i=0; i<2; i++) {
      //cout << "main() : creating thread, " << i << endl;
      td_array[i].thread_id=i;
      td_array[i].rt_ptr = &rt;
      td_array[i].img_ptr = &image;
      td_array[i].scene_ptr = &scene;
      //cout << "td_array.thread_index: " << td_array[i].thread_id << endl;
      rc = pthread_create (&threads[i], NULL, RayTracer::run_thread, &td_array[i]);
  }
   if (rc) {
      cout << "Error:unable to create thread," << rc << endl;
      exit(-1);
   }
   pthread_attr_destroy(&attr);
   for (int i=0; i<2; i++ ) {
      rc = pthread_join(threads[i], &status);
      if (rc) {
         cout << "Error:unable to join," << rc << endl;
         exit(-1);
      }
   }
//tgaManager.writeImage (rt,image.getSize());

  for (int i=0; i<image.getWidth() * image.getHeight(); i++) {
    cout << i << endl;
    tgaManager.file_writer.put (rt.b[i]);
    tgaManager.file_writer.put (rt.c[i]);
    tgaManager.file_writer.put (rt.d[i]);
  }
tgaManager.closeFile(1);
rt.deleteImgPtr (); 
}

【问题讨论】:

  • 记住std::thread 存在。
  • 您应该衡量花费的时间太长。它是同步或渲染或预处理(准备)或后处理(加入)。因此,在单线程和多线程版本中测量代码的每个部分的持续时间并进行比较。您发现不一致的部分是您的问题。它可能就像慢速像素访问或锁定争斗或使用顺序非线程 api 一样简单......还要确保你的线程不会渲染相同的像素......我通常会为线程安排整行图像,所以如果你有 N-每个Nth + i 行的核心都转到i-th 核心。
  • 根据我的经验,使用像素粒度往往会变慢。
  • 感谢您的回答。我能够使用像素级粒度实现多线程,现在有了真正的性能提升。尽管如此,当我为每个线程做同样的工作时发生的一个问题,现在仍然在发生(我希望当我改变算法时它会消失)看看这个链接:postimg.cc/image/9apkpgxet。飞机的蓝色应该是平滑的。巧合与否,这只发生在这个测试用例中,有一架飞机。我会把它改成正常的,并用单线程比较颜色值,但如果你知道来源,我想听听

标签: c++ multithreading graphics parallel-processing raytracing


【解决方案1】:

你确实想加入()线程,因为如果你不这样做,你有几个问题:

  1. 您如何知道线程何时完成执行?您不想开始写出结果图像,却发现在写出它的那一刻它还没有完全计算出来。

  2. 您如何知道何时可以安全地拆除线程可能正在访问的任何数据结构?例如,您的 RayTracer 对象在堆栈上,并且 (AFAICT) 您的线程正在写入其像素数组。如果您的 main 函数在线程退出之前返回,则很有可能线程有时最终会写入不再存在的 RayTracer 对象,这将通过覆盖可能存在的任何其他对象来破坏堆栈(碰巧)在您的函数返回后的相同位置。

所以你肯定需要 join() 你的线程;不过,您不需要将它们显式声明为 PTHREAD_CREATE_JOINABLE,因为无论如何该属性已经默认设置。

加入线程不应该导致线程变慢,只要两个线程都被创建并运行之前你对它们中的任何一个调用 join() (这似乎是你的情况贴出代码)。

至于为什么您会看到两个线程的速度变慢,这很难说,因为速度变慢可能来自多个地方。一些可能性:

  1. 您的光线追踪代码中的某些内容锁定了一个互斥体,因此对于大部分光线追踪运行,无论如何一次只允许两个线程中的一个执行。

    李>
  2. 两个线程几乎同时写入相同的内存位置,这会导致缓存争用,从而减慢两个线程的执行速度。

我的建议是设置你的线程,使线程#1 只呈现图像的上半部分,线程#2 只呈现图像的下半部分;这样,当他们写入输出时,他们将写入内存的不同部分。

如果这没有帮助,您可以暂时将渲染代码替换为更简单的代码(例如,将像素设置为随机值的“渲染器”),看看您是否可以看到这样的加速。如果是这样,那么您的 RayTracer 的实现中可能存在一些对多线程不友好的东西。

【讨论】:

  • 所以我可以摆脱:pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); ?我也可以摆脱 pthread_attr_t attr 吗? pthread_attr_init(&attr);pthread_attr_destroy(&attr);如果可能,请查看我在原始帖子中的回复 Spektre,看看我是否可能对问题有所了解。
  • 是的,两个问题
猜你喜欢
  • 1970-01-01
  • 2017-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-20
  • 2016-05-22
  • 1970-01-01
相关资源
最近更新 更多