【问题标题】:Is ChronicleMap an improved Concurrent HashMap?ChronicleMap 是改进的并发 HashMap 吗?
【发布时间】:2021-03-01 18:02:07
【问题描述】:

我是 JVM 中堆外存储的新手,ChronicleMap 看起来很适合堆外存储。但我主要关心的是性能。

我确实使用配置运行了简单的测试

ChronicleMapBuilder<IntValue, BondVOImpl> builder =
                ChronicleMapBuilder.of(IntValue.class, BondVOImpl.class)
                .minSegments(512)
                .averageValue(new BondVOImpl())
                .maxBloatFactor(4.0)
                .valueMarshaller(new BytesMarshallableReaderWriter<>(BondVOImpl.class))
                .entries(ITERATIONS);

发现以下结果

----- Concurrent HASHMAP ------------------------
Time for putting 7258
Time for getting 678

----- CHRONICLE MAP ------------------------
Time for putting 4704
Time for getting 2246

与 Concurrent HashMap 相比,读取性能相当低。

我已经尝试使用 1024/2048 段,默认膨胀因子,默认 Marshaller。但结果还是一样。

我只是想利用堆外特性来减少 GC 暂停,并且无意使用持久性事物或复制或使用 JVM 之外的映射。

所以问题是我应该使用 ChronicleMap 还是坚持使用 ConcurrentHashMap? 或者在 ChronicleMap 的情况下,我可以使用任何其他配置来提高性能?

提前致谢。

** 使用https://github.com/OpenHFT/Chronicle-Map/blob/master/src/test/java/net/openhft/chronicle/map/perf/MapJLBHTest.java 进行基准测试:**

【问题讨论】:

  • 您不能在不编组对象的情况下使用堆外存储。当然,这比仅仅存储一个引用更昂贵。只有您可以回答这个重要问题,这种方法是否真正“减少了您的设置中的 GC 暂停”?多少钱?将收益与成本进行比较,然后再决定……
  • @Holger 我建议你回答你的评论,这样他的问题就可以被标记为已解决。
  • 1) 你是如何测试得到这些数字的——没有代码和证明,它们是无关紧要的。 2)堆外不一定是关于较低的暂停时间。如果你真的想要更短的暂停时间,你应该先采取一些小步骤,比如用你的 current 收集器分析 GC 日志(可能会改进),然后很可能转移到 ShenandoahZGC。事实上,我们前段时间在我们的代码库中使用了堆外,并在我们迁移到 Shenandoah 时将其完全删除。
  • 这不是一个有用的比较。 ConcurrentHashMap,顾名思义,专为并发更新而设计。当您要填写一次地图,然后只进行读取访问时,普通的HashMap 就足够了。对于现实生活中的案例,您必须使用不同的参数执行多个测试,例如不同的数据量,不同的并发性,不断变化的读写模式,然后,看看地图如何响应这些变化的参数。并且具有较大的 GC 暂停时间并不能证明 ChronicleMap 会改善这种情况。只有实际测试才能判断。
  • 你没有抓住重点。 ConcurrentHashMap 擅长处理并发写入,而您没有检查 ChronicleMap 的执行情况。也许,单线程一次性填充是唯一一个ChronicleMap 似乎更快的操作,而且也只有在执行不充分的基准测试时。你确定你考虑过How do I write a correct micro-benchmark in Java?中讨论的每一点吗?我怀疑它,因为那时你已经检查了环境参数的影响。

标签: java garbage-collection jvm chronicle-map off-heap


【解决方案1】:

首先,我不买你的测试结果。您没有为您的基准测试提供任何代码,我怀疑基准测试相当不准确(是的,基准测试是相当复杂的主题,没有预热和所有相关的东西它是没有意义的)。我们的基准测试给了我这个:

-------------------------------- SUMMARY (Read) -----------------------------------------------------------
Percentile   run1         run2         run3      % Variation
50:             0.16         0.16         0.21        17.15
90:             0.23         0.20         0.35        33.48
99:             0.46         0.43         0.78        35.19
99.7:           0.74         1.22         1.59        16.83
99.9:           1.52         1.85         2.84        26.06
worst:         36.46      5187.58       161.09        95.41
-------------------------------------------------------------------------------------------------------------------
-------------------------------- SUMMARY (Write) -----------------------------------------------------------
Percentile   run1         run2         run3      % Variation
50:             2.67         2.69         3.05         8.21
90:             3.02         2.95         3.97        18.75
99:             4.51         6.20         9.06        23.50
99.7:           5.86         9.28        15.55        31.07
99.9:         930.56        22.10       964.86        96.60
worst:       1357.31    226033.66    233373.70         2.12
-------------------------------------------------------------------------------------------------------------------

数字以微秒为单位,基准代码在这里https://github.com/OpenHFT/Chronicle-Map/blob/master/src/test/java/net/openhft/chronicle/map/perf/MapJLBHTest.java

我们有证据表明,在大多数情况下,Chronicle Map 比 ConcurrentHashMap 更好 - 但这取决于编组的实施情况。

也就是说,替换 ConcurrentHashMap 并不是 Chronicle Map 的主要用例。

  • 堆外映射能够存储大量数据,这是堆数据结构无法比拟的,而且不会造成巨大的性能损失。
  • 在持久模式下,它可以在多个进程之间使用
  • 可以在主机之间复制

【讨论】:

  • 无论“编组的实现有多好”,它都无法与仅传递引用的操作竞争。由于堆外内存的实际优势是有意减少(堆)内存管理开销,因此您无法使用仅获取和放置的简单微基准来衡量它。
  • @Holger 在地图中放置和获取并不是“简单地传递参考”。哈希表查找是 put 和 get 操作的主要成本,尤其是在竞争环境中,这在 Chronicle Map 中非常有效。此外,Values 库可以轻松地与传递引用竞争——它不会将数据反序列化到堆对象中,而是提供一个享元,它保存对原始字节的引用,并且该对象上的访问器直接与堆外内存对话。跨度>
  • 基本上,尽管使用 Chronicle Map 的主要好处来自其“堆外”,但正如文档所示,它可以用作 ConcurrentHashMap 的替代品
  • @DmitryPisklov 将其用作“ConcurrentHashMap 的替代品”和“提供对原始字节的引用的享元”是矛盾的。后者显然仅在特定用例中的值类型可以被享元对象替换时才有效。此外,享元对象仍然是一个堆对象,即使它的属性存储在堆外。您需要连接到这些属性的大量数据才能使该间接寻址比普通堆对象更有效。将其宣传为“直接替代”是值得怀疑的。这是一个专门的工具。
  • 所以我们同意,当使用 ChronicleMap 作为 ConcurrentHashMap 的替代品时,正如 OP 显然所做的那样,编组是不可避免的?
猜你喜欢
  • 1970-01-01
  • 2013-03-12
  • 1970-01-01
  • 2015-09-06
  • 2012-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多