【问题标题】:Rendering image using Multithread使用多线程渲染图像
【发布时间】:2015-03-01 07:34:19
【问题描述】:

我有一个光线追踪算法,它只适用于 1 个线程,我正在尝试让它适用于任意数量的线程。

我的问题是,我可以通过哪种方式在线程之间分配此任务。

起初我的教练告诉我只划分图像的宽度,例如如果我有一个 8x8 的图像,我想要 2 个线程来完成任务,让线程 1 渲染 0 到 3 个水平区域(当然所有垂直向下)和线程 2 渲染 4 到 7 个水平区域。

当我的图像长度和线程数都是 2 的幂时,我发现这种方法非常有效,但我不知道如何处理奇数线程或无法在没有提醒的情况下划分宽度的任意线程.

我解决这个问题的方法是让线程交替渲染图像,例如,如果我有一个 8x8 的图像,并且让我们说如果我有 3 个线程。

线程 1 在水平方向渲染像素 0,3,6

线程 1 在水平方向渲染像素 1,4,7

线程 1 在水平方向渲染像素 2,5

抱歉,我无法提供所有代码,因为有超过 5 个文件,每个文件有几百行代码。

这是在水平区域循环的for循环,垂直循环在其中,但我不打算在这里提供。

我的导师的建议

for( int px=(threadNum*(width/nthreads)); px < ((threadNum+1)*(width/nthreads)); ++px )

threadNum 是我所在的当前线程(意思是线程 0、1、2 等等) width 是图像的宽度 nthreads 是线程的总数。

我对这个问题的解决方案

for( int px= threadNum; px< width; px+=nthreads  )

我知道我的问题不是很清楚,很抱歉,但我不能在这里提供整个代码,但基本上我要问的是哪种方式是在给定数量的线程之间划分图像渲染的最佳方式(可以是任何正数)。另外我希望线程按列渲染图像,这意味着我无法触及处理垂直渲染的代码部分。

谢谢,对混乱的问题表示抱歉。

【问题讨论】:

  • 分割不会有问题,因为剩下的部分覆盖了剩余部分。
  • 很好的答案,谢谢。

标签: c multithreading


【解决方案1】:

首先,让我告诉你,在每个像素的渲染独立于其他像素的假设下,你的任务就是 HPC 领域中所谓的“令人尴尬的并行问题” ;也就是说,一个可以在任意数量的线程之间有效划分的问题(直到每个线程都有一个“工作单元”),进程之间没有任何相互通信(这非常好)。

也就是说,这并不意味着任何并行化方案都与其他方案一样好。对于您的具体问题,我想说要记住的两个主要因素是负载平衡缓存效率

负载平衡意味着您应该在线程之间分配工作,使每个线程的工作量大致相同:这样可以防止一个或多个线程等待最后一个必须完成的线程工作。

例如

您有 5 个线程,您将图像分成 5 个大块(比如说 5 个水平条,但它们可以是垂直的,并且不会改变点)。作为令人尴尬的并行问题,您期望获得 5 倍的加速,但实际上却只有微不足道的 1.2 倍。

原因可能是您的图像在图像的下部具有大部分计算成本很高的细节(我对渲染一无所知,但我认为反射对象可能需要更多时间来渲染比平坦的空白空间),因为它是由空框架上的地板上的一组抛光金属大理石组成的。

在这种情况下,无论如何,只有一个线程(图像底部 1/5 的那个)完成所有工作,而其他 4 个线程在完成简短任务后保持空闲状态。

正如您可以想象的那样,这不是一个好的并行化:仅考虑负载平衡,最好的并行化方案是将交错的像素分配给每个内核以供它们处理,在(非常合理的)假设下图像的复杂性将在每个线程上进行平均(对于自然图像来说是这样,在非常有限的场景中可能会产生惊喜)。

使用此解决方案,您的图像在像素之间均匀分布(统计上),最坏的情况是 N-1 个线程等待单个线程计算单个像素(您不会注意到,性能方面)。

为此,您需要循环遍历 所有像素,以这种方式忘记线条(伪代码,未经测试):

for(i = thread_num; i < width * height; i+=thread_num)

第二个因素,缓存效率与计算机的设计方式有关,具体而言,它们具有多层缓存以加速计算并防止 CPU 饿死(在等待时保持空闲对于数据),并且以“正确的方式”访问数据可以大大加快计算速度。

这是一个非常复杂的主题,但在您的情况下,经验法则可能是“为每个线程提供适量的内存将改进计算”(强调“适量”的意图......)。

这意味着,即使将交错像素传递给每个线程可能是完美的平衡,这也可能是您可以设计的最糟糕的内存访问模式,您应该将“更大的块”传递给它们,因为这会保留 CPU忙(注意:内存对齐也很重要:如果您的图像在每行之后都有填充,请保持它们的倍数,例如 32 字节,就像某些图像格式一样,您应该考虑到它!)

如果不将已经冗长的答案扩展到惊人的大小,这就是我要做的(我假设图像的内存是连续的,行之间没有填充!):

  1. 为每个 M 个线程创建一个程序,将图像分成 N 个连续像素(使用预处理器常量或 N 的命令参数,以便您可以更改它!),如下所示:

    1111111122222222333333334444444411111111

  2. 对 N 的各种值进行一些分析,从 1 步进到 2048,假设是 2 的幂(要测试的好值可能是:1 获得基线,32、64、128, 256、512、1024、2048)

  3. 找出完美负载平衡 (N=1) 和最佳缓存(N
  4. a 在多个系统上尝试该程序,并保持 N 的较小值,以便在机器中为您提供最佳测试结果,以使您的代码在任何地方都能快速运行(因为缓存细节各不相同系统之间)。
  5. b 如果您真的 真的想从安装代码的每个系统中挤出每个周期,忘记第 4a 步,并创建一个代码在处理指定任务之前,通过渲染一个小的测试图像自动找出 N 的最佳值:)
  6. 胡乱使用 SIMD 指令(开个玩笑……有点:))

有点理论(而且太长了......),但我仍然希望它有所帮助!

【讨论】:

    【解决方案2】:

    列的交替划分可能会导致缓存使用不理想。线程应该对更大的连续数据范围进行操作。顺便说一句,如果您的图像是按行存储的,那么分布行而不是列也会更好。

    这是用任意数量的线程平均划分数据的一种方法:

    #define min(x,y) (x<y?x:y)
    /*...*/
    int q = width / nthreads;
    int r = width % nthreads;
    int w = q + (threadNum < r);
    int start = threadNum*q + min(threadNum,r);
    for( int px = start; px < start + w; px++ )
      /*...*/
    

    剩余的r 分布在第一个r 线程上。这在计算线程的开始索引时很重要。

    对于 8x8 图像,这将导致:

    • 线程 0 呈现列 0-2
    • 线程 1 呈现第 3-5 列
    • 线程 2 呈现第 6-7 列

    【讨论】:

      猜你喜欢
      • 2022-07-20
      • 1970-01-01
      • 2015-06-28
      • 2015-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多