【发布时间】:2015-06-12 19:42:27
【问题描述】:
假设我有一个包含 1,000,000 个元素的数组,以及多个工作线程,每个线程都在处理这个数组中的数据。工作线程可能正在使用新数据更新已填充的元素,但每个操作仅限于单个数组元素,并且独立于任何其他元素的值。
使用单个互斥锁来保护整个数组显然会导致高争用。在另一个极端,我可以创建一个与原始数组长度相同的互斥体数组,并且对于每个元素array[i],我会在对其进行操作时锁定mutex[i]。假设数据分布均匀,这将主要消除锁争用,但会消耗大量内存。
我认为更合理的解决方案是拥有一组n 互斥体(其中 1 array[i],我会在操作时锁定mutex[i % n]。如果n 足够大,我仍然可以尽量减少争用。
所以我的问题是,除了增加内存使用量之外,以这种方式使用大量(例如 >= 1000000)互斥量是否会降低性能?如果是这样,您可以合理使用多少互斥锁才能开始看到降级?
我确信这个问题的答案在某种程度上是特定于平台的;我在 Linux 上使用 pthreads。我也在努力建立自己的基准,但我正在处理的数据规模使得这很耗时,因此我们将不胜感激。
这是最初的问题。对于那些询问有关该问题的更详细信息的人,我有 4 个多 GB 的二进制数据文件,描述了正在分析的大约 50 亿个事件附近的某个地方。有问题的数组实际上是支持一个非常大的链式哈希表的指针数组。我们将这四个数据文件读入哈希表,如果它们共享某些特征,可能会将它们聚合在一起。现有的实现有 4 个线程,每个线程读取一个文件并将该文件中的记录插入到哈希表中。哈希表有 997 个锁和 997*9973 = ~10,000,000 个指针。在插入带有哈希h的元素时,我先锁定mutex[h % 997],然后再插入或修改bucket[h % 9943081]中的元素。这没问题,据我所知,我们没有太多的争用问题,但存在性能瓶颈,因为我们只使用了 16 核机器的 4 核。 (因为文件的大小通常不一样,所以我们会更少。)一旦所有的数据都被读入内存,然后我们分析它,它使用新线程和新的锁定策略调整到不同的工作量。
我正在尝试通过切换到线程池来提高数据加载阶段的性能。在新模型中,我仍然为每个文件设置一个线程,它只是以大约 1MB 的块读取文件,并将每个块传递给池中的工作线程以进行解析和插入。到目前为止,性能提升很小,我所做的分析似乎表明锁定和解锁阵列所花费的时间可能是罪魁祸首。锁定内置于我们正在使用的哈希表实现中,但它确实允许指定要使用的锁的数量,而与表的大小无关。我希望在不改变哈希表实现本身的情况下加快速度。
【问题讨论】:
-
哪个操作系统?我在 CentOS 上尝试过一次,使用约 1M 互斥体的质数,而不是约 1K 互斥体的质数(哦,顺便说一句,使用质数),并且由于我从未发现的原因,性能受到了巨大的影响。
-
方案A:锁定整个阵列。你是对的:这很简单……但可能会导致高争用。计划 B:为每个元素创建和管理互斥体。可能不是一个好主意...建议:考虑read write,或“RCU”锁:docs.oracle.com/cd/E19455-01/806-5257/6je9h032u/index.html
-
@paulsm4 读/写锁对这种特殊情况没有帮助,因为我正在从输入文件构建数据结构。此时数据结构本质上是“只写”的,因此共享阅读器不会有帮助。
-
@AmiTavory 这是 Amazon EC2 实例上的 AWS linux,因此它应该与 CentOS 非常相似。我最初的实现使用了 997 个互斥锁。到目前为止,增加到 9973 似乎 减少而不是增加吞吐量,但我仍在努力设置有用的性能测试。您的经历与我的怀疑相符,但我很想知道为什么会这样。
-
我只想指出,几乎总有更好的方法来构建事物,而不是让大量线程都紧紧地争夺对同一个集合的访问权。如果我们更多地了解集合中的对象类型以及对它们执行的操作类型,我们可能会想出一个解决方案,让线程不那么激烈。
标签: c multithreading pthreads