填充值大小变化很大的 ChronicleMap 时出现 IllegalArgumentException

IllegalArgumentException when populating a ChronicleMap with high variability in value size

不久前,我问 关于将 ChronicleMap 用作 Map<String,Set<Integer>> 的问题。基本上,我们有一个集合,其中平均 Set<Integer> 可能是 400,但最大长度是 20,000。对于 ChronicleMap 2,这会导致相当严重的 JVM 崩溃。我转移到 ChronicleMap 3.9.1,现在开始出现异常(至少不是 JVM 崩溃):

java.lang.IllegalArgumentException: Entry is too large: requires 23045 chucks, 6328 is maximum.
    at net.openhft.chronicle.map.impl.CompiledMapQueryContext.allocReturnCode(CompiledMapQueryContext.java:1760)
    at net.openhft.chronicle.map.impl.CompiledMapQueryContext.allocReturnCodeGuarded(CompiledMapQueryContext.java:120)
    at net.openhft.chronicle.map.impl.CompiledMapQueryContext.alloc(CompiledMapQueryContext.java:3006)
    at net.openhft.chronicle.map.impl.CompiledMapQueryContext.initEntryAndKey(CompiledMapQueryContext.java:3436)
    at net.openhft.chronicle.map.impl.CompiledMapQueryContext.putEntry(CompiledMapQueryContext.java:3891)
    at net.openhft.chronicle.map.impl.CompiledMapQueryContext.doInsert(CompiledMapQueryContext.java:4080)
    at net.openhft.chronicle.map.MapEntryOperations.insert(MapEntryOperations.java:156)
    at net.openhft.chronicle.map.impl.CompiledMapQueryContext.insert(CompiledMapQueryContext.java:4051)
    at net.openhft.chronicle.map.MapMethods.put(MapMethods.java:88)
    at net.openhft.chronicle.map.VanillaChronicleMap.put(VanillaChronicleMap.java:552)

我怀疑这仍然是因为我的值远远超出平均值。我假设 ChronicleMap 根据我给构建器的平均值确定最大块数为 6328,但没想到会有一个巨大的值需要 23045 个块。

所以我的问题是:解决这个问题的最佳方法是什么?我正在考虑的一些方法,但仍然不确定:

  1. 使用ChronicleMapBuilder.maxChunksPerEntryChronicleMapBuilder.actualChunkSize。那就是说,我如何确定性地弄清楚应该设置什么?另外,如果设置得太高,这可能会导致大量碎片和性能下降,对吗?
  2. 有一个 "max collection size" 并将非常大的集合分成许多较小的集合,相应地设置密钥。例如,如果我的密钥是 XYZ,它会产生大小为 10000 的 Set<Integer>,也许我可以将其拆分为 5 个密钥 XYZ:1XYZ:2 等,每个密钥都有一个集合大小为 2000。这感觉就像是围绕我可以在 ChronicleMap 中配置的东西进行黑客攻击,并导致大量代码感觉好像没有必要。我在另一个问题中也提到了同样的计划。

其他 thoughts/ideas 表示赞赏!

如果您不手动指定 maxChunksPerEntry(),则条目的最大大小受 段层 大小的限制,以块为单位。所以你需要做的是使段层大小更大。您可以尝试做的第一件事是配置 actualSegments(1), if you are not going to access the map from multiple threads within the JVM concurrently. You have additional control over those configurations via ChronicleMapBuilder.actualChunkSize(), actualChunksPerSegmentTier() and entriesPerSegment().

默认情况下,ChronicleMapBuilder 选择的块大小介于配置的平均值大小的 1/8 和 1/4 之间。因此,如果您的段层大小为 6328 个块,则您的段配置为包含大约 1000 个条目。如果您的平均值集大小有 400 个元素,最大值为 20,000,则平均值和最大值之间的差异应该约为 50 倍,但从堆栈跟踪来看,您的一个条目似乎比平均值大 2000 倍以上。可能你还没有算到什么。

另外,对于如此大的值,我建议开发和使用内存效率更高的值序列化程序,因为默认值序列化程序会产生大量垃圾。例如它可以使用原语 IntSet,它实现了来自 fastutil 或 Koloboke 或 Koloboke Compile 库的 Set<Integer>

另外我建议使用现在可用的最新版本,Chronicle Map 3.9.1 已经过时了。