不完整的多线程光线追踪器花费的时间是预期的两倍

Incomplete multi-threading RayTracer taking twice as much time as expected

本文关键字:两倍 多线程 光线追踪 时间      更新时间:2023-10-16

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

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

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

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

pthread_exit (NULL); 

作为它上面的最后一件事(它上面唯一的 MT 东西(。然后执行另一个循环以连接线程。最后,我星星在一个小循环中写入文件。最后关闭文件并删除与用于在 draw 方法中存储图像的数组相关的指针。

现在我可能不需要使用 join,因为我不是在做"真正的"多线程光线追踪器,只是渲染它两次,但是一旦我开始在图像像素之间交替(即 thread0 ->渲染像素0 - 线程 0 -> 存储像素 0,线程 1 ->渲染像素 1 - 线程 1 -> 存储像素 1,线程 0 -> 渲染像素 2 - 线程 0 -> 存储像素 2, 线程 1 -> 渲染像素3 - 线程 1 -> 存储像素 3,等等...我想我需要它才能在文件上以正确的顺序写入像素。

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

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

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

  1. 你怎么知道线程何时完成执行? 您不想开始写出生成的图像,却发现在写出图像时它没有完全计算。

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

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

加入线程不会导致线程变慢,只要在对其中任何一个线程调用 join((之前创建并运行两个线程(在您发布的代码中似乎是这种情况(。

至于为什么你会看到两个线程的放缓,这很难说,因为放缓可能来自多个地方。 一些可能性:

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

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

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

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