7

我一直在尝试理解并发,并且一直在尝试找出更好的方法,一个大IORef锁或多个TVars。我已经得出以下指导方针,我们将不胜感激,关于这些是否大致正确或我是否错过了重点。


让我们假设我们的并发数据结构是一个映射m,像 一样访问m[i]。还可以说我们有两个函数,f_easy并且f_hard. f_easy速度很快,需要f_hard很长时间。我们将假设 的参数f_easy/f_hard是 的元素m

(1) 如果您的交易大致如下所示m[f_easy(...)] = f_hard(...),请使用IORefwith atomicModifyIORef。懒惰将确保m它只被锁定很短的时间,因为它是用一个 thunk 更新的。计算索引有效地锁定了结构(因为有些东西会被更新,但我们还不知道是什么),但是一旦知道那个元素是什么,整个结构上的 thunk 就会移动到仅在那个特定元素上的 thunk ,然后只有那个特定的元素被“锁定”。

(2) 如果你的交易看起来大致是这样m[f_hard(...)] = f_easy(...)的,并且不要冲突太多,使用很多TVars。在这种情况下使用 anIORef将有效地使应用程序成为单线程,因为您不能同时计算两个索引(因为整个结构上会有一个未解决的 thunk)。TVars 让你同时计算出两个索引,但是,不利的是,如果两个并发事务都访问同一个元素,并且其中一个是写入,则必须废弃一个事务,这会浪费时间(本来可以在别处使用)。如果这种情况经常发生,你可能会更好地使用来自 (通过黑洞)的锁IORef,但如果它不经常发生,你将获得更好的TVars 并行性。

基本上在情况(2)中,IORef您可能会获得 100% 的效率(没有浪费的工作)但只使用 1.1 个线程,但TVar如果您的冲突数量较少,您可能会获得 80% 的效率但使用 10 个线程,所以您仍然结束即使浪费了工作,速度也提高了 7 倍。

4

2 回答 2

5

您的指导方针有点类似于 [1](第 6 节)的结果,其中分析了 Haskell STM 的性能:

“特别是对于那些在事务内部不执行太多工作的程序,提交开销似乎非常高。为了进一步观察这种开销,需要对提交时粗粒度和细粒度的性能进行分析STM 锁定机制。”

我使用atomicModifyIORefMVar当我需要的所有同步都是简单锁定将确保的东西。在查看对数据结构的并发访问时,还取决于该数据结构的实现方式。例如,如果您将数据存储在 a 中IORef Data.Map并经常执行读/写访问,那么我认为atmoicModifyIORef会降级为单线程性能,正如您所猜想的那样,但对于 a 也是如此TVar Data.Map。我的观点是使用适合并发编程的数据结构很重要(平衡树不是)。

也就是说,在我看来,使用 STM 的获胜论点是可组合性:您可以将多个操作组合成一个事务而不会令人头疼。IORef通常,无论MVar是否引入新锁 ,这都是不可能的。

[1] 软件事务内存 (STM) 的限制:在多核环境中剖析 Haskell STM 应用程序。 http://dx.doi.org/10.1145/1366230.1366241

回答@Clinton 的评论:

如果单个 IORef包含您的所有数据,您可以简单地atomicModifyIORef用于组合。但是,如果您需要处理对该数据的大量并行读/写请求,则性能损失可能会变得很大,因为对该数据的每一对并行读/写请求都可能导致冲突。

我会尝试的方法是使用数据结构,其中条目本身存储在 a 中TVar(而不是将整个数据结构放入单个TVar)。这应该会减少活锁的可能性,因为事务不会经常发生冲突。

当然,您仍然希望使您的事务尽可能小,并且仅在绝对有必要保证一致性时才使用可组合性。到目前为止,我还没有遇到过需要将多个插入/查找操作组合到单个事务中的情况。

于 2012-04-19T02:45:29.707 回答
1

除了性能之外,我认为使用TVar类型系统还有一个更根本的原因,可以确保您不会执行任何“不安全”操作,例如readIORefor writeIORef。共享您的数据是类型的属性,而不是实现的属性。编辑:unsafePerformIO总是不安全的。 readIORef仅当您还使用atomicModifyIORef. 至少将您的 IORef 包装在一个新类型中,并且只公开一个包装好的atomicModifyIORef

除此之外,不要使用IORef,使用MVarTVar

  1. 您描述的第一个使用模式可能没有很好的性能特征。您可能最终(几乎)完全是单线程的——由于懒惰,每次更新共享状态时都不会发生实际工作,但是每当您需要使用此共享状态时,都需要强制执行整个累积的 thunk 堆,并且具有线性数据依赖结构。
  2. 拥有 80% 的效率但显着更高的并行度允许您利用不断增长的内核数量。您可以期望在未来几年对单线程代码的性能改进最少。
  3. 许多词 CAS 可能会以“硬件事务内存”的形式出现在您附近的处理器中,从而使 STM 变得更加高效。
  4. 您的代码将更加模块化——当您的设计在单个引用后面具有所有共享状态时,如果您添加更多共享状态,则必须更改每段代码。 TVars并且在较小程度上MVars支持自然模块化。
于 2012-04-19T02:39:53.880 回答