【发布时间】:2014-04-23 04:53:47
【问题描述】:
我目前正在实现一个多线程体素游戏引擎。当我使用多线程时,我很快就遇到了互斥锁的性能瓶颈。
为了澄清我的问题,让我们来看一个 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,并使后面的八个作业都等待互斥锁,从而降低性能。
目前我能想到的唯一缓解方法是尝试以分散的方式添加作业,希望我们可以避免大多数停顿。但我不喜欢这种方法,因为它看起来更像是一种解决方法,而且如果锁定模式发生变化,它也不是很灵活。
我也想过使用“异步互斥锁”。但我不太确定该怎么做。
编辑: 澄清一下,照明作业是在运行时添加的,而不是按固定顺序添加的。例如,假设玩家从当前处理的区块中移出,那么只有外面的区块应该被添加到队列中,队列可能处于不规则的边界。
所以我认为仅仅使用一个像样的调度器是不足以解决这个问题的。
【问题讨论】:
-
假设您的体素块不是太大,是否可以复制受影响的块,处理它们并在最后合并结果?例如。线程 1 拉出块 A,发现它需要在块 C 和 F 上执行光照。然后它复制 C 和 F 并继续处理它们。线程 2 拉出块 B 并看到还需要在 C 上工作。它还复制 C 并对其进行工作。处理完所有块后,主线程从每个线程获取结果并执行加法混合以获得最终的光照贴图。
-
@Wes 聪明的主意。但是我使用的大多数体素算法(包括照明)应该具有接近 O(N) 级别的性能(N 是块大小)。在主线程中访问所有块的附加步骤只会使多线程无用。
-
整个代码块是互斥的吗?也就是说,如果您在
A运行时无法为B做任何事情,只需将B移动到队列的后面并获取下一个项目。当然,如果您已经为B做了一些工作,这可能不起作用。我猜你不想回滚工作。 -
@Heuster:是互斥的。或者至少我必须像@Wes 所说的那样做额外的混合步骤。你的方法应该在理论上有效。但是如何存储和获取谁在处理哪些块的信息呢?在上图中,我按顺序锁定互斥锁(例如,对于
E,锁定A,B,C,...,I,一个接一个)。因为我们不能一次原子地锁定所有九个互斥锁,而这种方案可以避免死锁。如果它没有互斥锁,或者可能使用 mutex.tryLock(),如何避免死锁(以及潜在的活锁)? -
好的,这样的碰撞多久发生一次?我知道这样的事件一定不能发生,但如果它们相对不频繁,应该可以设计一个更好的防冲突策略,在非冲突情况下避免九个互斥锁,但在实际检测到冲突时需要更多操作.这将提高整体性能。
标签: c++ multithreading algorithm mutex voxel