减少多线程体素算法中的互斥开销

时间:2014-04-23 04:53:47

标签: c++ multithreading algorithm mutex voxel

我目前正在实施一个多线程体素游戏引擎。当我使用多线程时,我很快遇到了使用互斥锁的性能瓶颈。

为了澄清我的问题,我们来看一个2D案例:

+-+-+-+
|A|B|C|
+-+-+-+
|D|E|F|
+-+-+-+
|G|H|I|
+-+-+-+

所有这些细胞都是体素块(16x16体素)。

我使用多个线程以块为单位执行所有体素算法。我有一个由要处理的块组成的作业队列,每个工作线程只是不断从队列中挑选块并对其进行处理。

现在想象一个线程需要在块E中进行一些光照计算。因为在E的角落可能会有一个光源传播到相邻的块,它必须锁定所有九个相邻的块以避免潜在的数据竞争,使用互斥锁。

然而,正如我的实验,互斥体的性能开销并不好。目前我正在使用简单的for循环来添加作业。因此,当游戏运行时,初始作业队列将变为:

A, B, C, D, E, F, G, H, I, ...

这非常糟糕,因为第一个作业A将锁定A,B,D,E并使所有后续八个作业等待互斥锁,从而导致性能下降。

目前我能想到的唯一缓解措施是尝试以分散的方式添加工作,希望我们可以避免大多数摊位。但我不喜欢这种方法,因为它看起来更像是一种解决方法,如果锁定模式发生变化,则不是很灵活。

我还想过使用“异步互斥体”。但我不太清楚该怎么做。

编辑: 只是为了澄清,照明作业是在运行时添加的,而不是按固定顺序添加的。例如,假设玩家移出当前处理的块,然后只应将外部块添加到队列中,队列可能处于不规则的边界。

所以我认为单独使用一个不错的调度程序不足以解决这个问题。

2 个答案:

答案 0 :(得分:1)

这太长了,不适合评论部分。

如何使用原子bool查看当前是否正在处理块?这样,您将获得更高的线程利用率,而不是等待进行处理的线程。此外,如果您在相同分散的点开始每个线程,您将获得更少的冲突。然后,当线程需要在相同的块上工作时,此算法才解决问题。

  1. 线程1获得块A(影响块B和C)并设置A处理标志。
  2. 线程2同时获得块B并设置B的处理标志。
  3. 线程1完成块A并重置A标志并检查块B是否可用。
  4. 线程2忙于B,所以线程1离开B未处理,现在移动到C并设置C标志。
  5. 线程2结束,B重置B标志。
  6. 线程1完成C并重置C标志,然后移动到B. B已经有线程2的结果,因此线程1在B上完成最终传递并完成。

答案 1 :(得分:0)

如何调度线程之间的一些偏移,如下所示:

offset-ed sheduling

  • 不同的颜色意味着不同的工作线程
  • 图片代表地图中的单行
  • 更多文字吼叫

您有4或8个neigbours,例如4个工作线程

  • 为清楚起见,单元格0102表示行= 01列-02(均从0开始计算)

    thread 0000 00001 0002 0003
    -------------------------
           cell cell cell cell
    -------------------------
           0000 0003 0006 0009
           0001 0004 0007 0010
           0002 0005 0008 0011
    
           0012 0015 0018 0021
           0013 0016 0019 0022
           0014 0017 0020 0023
    
           0024 0027 0030 0033
           0025 0028 0030 0034
           0026 0029 0030 0035
    
           ... till end of row
    
           0100 0103 0106 0109
           0101 0104 0107 0110
           0102 0105 0108 0111
    
           0112 0115 0118 0121
           0113 0116 0119 0122
           0114 0117 0120 0123
    
           0124 0127 0130 0133
           0125 0128 0130 0134
           0126 0129 0130 0135
    
           ... till end of row
           ... till end map
    
  • 线程之间的差距越大越好

  • 最少2个空格
  • 当命中行结束时,应等待在安排下一行之前完成所有操作
  • 你也可以完全避免冲突,因为你先将差距边缘,然后是其余部分
    • 而不是0000,0001,0002将是0002,0000,0001

此方法可以创建数据传播工件!!!

  • 因为数据不是在邻居之间连续传播
  • 因此可能会出现一些类似网格的工件......