【问题标题】:Is there a parallel processing implementation of HashMap available to Java? Is it even possible?是否有可供 Java 使用的 HashMap 的并行处理实现?甚至可能吗?
【发布时间】:2009-09-04 23:10:01
【问题描述】:

寻找神奇的 ParallelHashMap 类

更简洁地说,您可以使用多个线程来加快 HashMap 查找速度吗?是否已经有任何实现可以做到这一点?

在我的项目中,我们需要在内存中维护一个大型对象映射。我们从不在地图创建后对其进行修改,因此地图是严格只读的。但是,此地图上的读取和查找性能对于应用程序的成功绝对至关重要。将安装应用程序的系统通常具有许多可用的硬件线程。然而,我们的查找只使用一个线程从 HashMap 中检索值。使用多个线程(可能在一个池中)的分而治之方法是否有助于提高查找速度?

我的大部分谷歌搜索都没有结果——返回大量关于并发问题而不是解决方案的结果。任何建议都将不胜感激,但如果您知道开箱即用的解决方案,那您就太棒了。

另外值得注意的是,所有键和值都是不可变的。哈希码值在实例化时预先计算并存储在对象本身中。

至于实现的细节,Map 中有大约 35,000 个项目。键和值都是对象。键是自定义查找键,值是字符串。目前,我们每秒最多可以处理大约 5,000 次查找(这包括一些其他逻辑的一些开销,但主要瓶颈是地图实现本身)。但是,为了跟上我们未来的性能需求,我希望这个数字达到每秒大约 10,000 次查找。按照大多数正常标准,我们当前的实现速度很快 - 只是我们需要它更快。

在我们的 35,000 个值的映射中,我们平均大约有一次哈希码冲突,所以我猜测哈希码分布合理。

【问题讨论】:

  • 您能解释一下为什么您的查找速度很慢吗?您的分析器发现了什么?

标签: java multithreading collections parallel-processing


【解决方案1】:

所以你的哈希码是预先计算的,equals 函数很快——在这种情况下,你的 hashmap 获取应该非常快。

您是否分析过您的应用程序以证明获得的 hashmap 确实是瓶颈?

如果您有多个应用程序线程,它们都应该能够同时从 hashmap 执行它们自己的获取 - 因为您没有修改映射,所以您不需要在外部同步获取。使用 hashmap 的应用程序是否有足够的线程来使用您的所有硬件线程?

由于哈希表的内容是不可变的,因此可能值得研究perfect hashing - 使用完美的哈希函数,您应该永远不会在哈希表中发生冲突或需要链接,这可能会提高性能。我不知道手头的 java 实现,但在 C/C++ 中知道,有 gperf

【讨论】:

  • +1 用于分析以避免“过早优化”,如果我可以确保散列函数创建分布良好、非冲突值,而不仅仅是理论上或随机数据,另一个 +1,还有生产数据。
【解决方案2】:

听起来你应该分析一下。你的碰撞率可能很高。您也可以尝试在 HashMap 中使用较低的 loadFactor - 以降低冲突概率。

另外,如果 hashCodes 是预先计算的,那么除了 mod 和几个 equals() 之外,get() 没有太多工作要做。关键对象上的 equals() 有多快?

【讨论】:

  • equals 应该很快,我正在通过 getter 引用的 5 个数字之间进行 equals 检查。
  • 您可以随时添加经典优化: if (this == other) { return true;但如果这不是瓶颈 - 不要打扰
  • 玩弄负载因子不太可能有帮助(因为 1.4.1ish 哈希在找到存储桶之前会重新哈希,因此高冲突计数往往表明整个 32 位哈希值匹配) .如果您有大量冲突,则哈希函数需要工作。
【解决方案3】:

回答您的问题:是的,当然。只要你不给它写信。

您将不得不手工制作,而且会有些棘手。在尝试之前,您是否已尽可能优化?

在 C++ 中,查看 Google 的 sparsehash 包中的密集哈希映射类。

在 Java 中,如果您使用原始键进行映射,请使用 Trove 或 Colt 映射。

也就是说,这是您的并行哈希映射的开始:如果您选择 n 个哈希函数并产生 n 个线程来搜索每条路径(在 n 个插入点中的每一个处进行探测/链接),您将获得不错的加速。小心,因为创建线程的成本很高,所以在构造时产生线程,然后阻塞它们直到需要它们。

希望锁定的成本不会高于查找的成本,但这取决于您自己进行试验。

【讨论】:

    【解决方案4】:

    来自HashMap documentation(我已经改变了重点):

    请注意,此实现不是 同步。如果多个线程 同时访问此地图,并在 至少有一个线程修改了 结构上的映射,它必须是 外部同步。

    由于您的 HashMap 从未被修改过,您可以安全地让多个线程从中读取。不需要实现锁定。 (对于线程共享对不可变数据的访问的任何情况都是如此;通常实现线程安全的最简单方法是不共享任何可写内存)

    为确保您的代码不会意外修改地图,我会在地图构建后立即用Collections.unmodifiableMap 包装地图。不要让对原始可修改地图的任何引用停留。

    【讨论】:

    • 我认为这不是以利亚想要的。他希望 get() 是多线程的。换句话说,他担心 get() 调用会太慢,他希望通过让多个线程在 get 上工作,它会更快地返回。
    • 还可以考虑 com.google.common.collect.ImmutableMap。它在设计上是不可修改的,因此它不需要代理(在此示例中其成本可能很高)来保护它不被修改。
    【解决方案5】:

    您在评论中提到了这一点:

    我正在对引用的 5 个数字进行相等检查

    由此我推断您的哈希计算也在用这 5 个数字进行一些计算。为了获得良好的 HashMap 性能,此计算的结果应随机分布在所有可能的 int 值上。来自HashMap documentation

    此实现提供 的恒定时间性能 基本操作(get 和 put), 假设散列函数分散 元素之间适当的 桶。

    换句话说,无论元素数量如何,查找时间都应该保持不变,如果你有一个好的散列函数。用于存储三个数字的类的良好 hashCode() 函数示例(使用 prime number 来减少 XOR 产生零的机会,如评论所建议的那样):

    return this.a.hashCode() ^ (31 * (this.b.hashCode() ^ (31 * this.c.hashCode())));
    

    错误 hashCode 函数示例:

    return (this.a + this.b + this.c);
    

    【讨论】:

    • 如果 ^ 哈希函数排列最终得到相同的代码。重复值完全消失。乘以一个素数,^ 是惯用的解决方案。
    【解决方案6】:

    HashMap 具有恒定的查找时间。不确定如何真正加快速度,因为尝试让多个线程执行散列函数只会导致它变慢。

    【讨论】:

    • 查找时间很大程度上取决于散列函数:从算法和实现的角度来看。
    • 你的回复真的和我的评论无关……从时序的角度看HashTables是常数时间。
    【解决方案7】:

    我认为您需要证据证明 HashMap 上的 get() 方法是您延迟的地方。我认为这是极不可能的。在您的 get() 方法周围放置一个循环,使其重复 1,000 次,您的应用程序可能根本不会变慢。然后你就会知道延迟在其他地方。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多