使用Multithread渲染图像

时间:2015-02-28 21:44:41

标签: c multithreading

我有一个光线跟踪算法,它只使用1个线程,我试图让它适用于任意数量的线程。

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

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

当我的图像长度和线程数都是2的幂时,我发现这种方法工作得很完美,但是我不知道如何处理奇数线程或任何数量的线程,如果没有提醒就无法划分宽度

我解决这个问题的方法是让线程通过交替渲染图像,例如,如果我有一个8x8图像,andlets说我有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  )

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

谢谢,抱歉混乱的问题。

2 个答案:

答案 0 :(得分:1)

首先,让我告诉你假设每个像素的渲染独立于其他像素,你的任务就是在HPC领域被称为&#34; embarassing并行问题&#34 ;;也就是说,一个问题可以在任意数量的线程之间进行有效划分(直到每个线程都有一个单独的工作单元&#34;),而且进程之间没有任何相互通信(非常好)。

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

负载平衡意味着您应该以每个线程具有大致相同工作量的方式在线程之间划分工作:这样可以防止一个或多个线程等待最后一个必须完成它的线程&# 39;最后的工作。

E.g。

你有5个线程,你将图像分成5个大块(让我们说5个水平条,但它们可以是垂直的,它不会改变点)。作为尴尬并行的问题,你期望加速5倍,而你只需要1.2倍。

可能的原因是你的图像在图像的下半部分具有大部分计算上昂贵的细节(我不知道渲染,但我认为反射对象可能需要更多的时间来渲染比平坦的空白空间),因为在空框架的地板上由一组抛光金属弹珠组成。

在这种情况下,只有一个线程(图像底部1/5的线程)完成所有工作,而其他4个线程在完成简短任务后仍然处于空闲状态。

你可以想象,这并不是一个很好的并行化:单独考虑负载均衡,最好的并行化方案是将交错像素分配给每个核心,供他们处理,在(非常合理的)假设下图像的复杂性将在每个线程上取平均值(对于自然图像,在非常非常有限的情况下可能会产生意外)。

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

要做到这一点,你需要循环所有像素忘记线条,这样(伪代码,未经测试):

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

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

这是非常复杂的主题,但在您的情况下,经验法则可能是&#34;为每个线程提供正确数量的内存将改善计算&#34; (强调&#34;正确的数量&#34;意图...)。

这意味着,即使传递到每个线程,交错像素可能是完美的平衡,它可能是您可能设计的最糟糕的存储器访问模式,并且您应该通过&#34;更大的块&#34;对他们来说,因为这会让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的smalles值,以便在机器中提供最佳测试结果,以使您的代码在任何地方都能快速运行(如缓存细节因系统而异。)
  5. b 如果确实 真的想要挤出您安装代码的每个系统的每个周期,请忘记步骤4a,然后创建通过在处理指定任务之前渲染小测试图像来自动找出N的最佳值的代码:)
  6. 愚弄SIMD指令(开玩笑......有点:) :)
  7. 有点理论上(过长......),但我希望它有所帮助!

答案 1 :(得分:0)

列的交替划分可能会导致次优缓存使用。线程应该在更大的连续数据范围内运行。顺便说一下,如果您的图像是按行存储的,那么分配行而不是列也会更好。

这是将数据与任意数量的线程平分的一种方法:

#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列