填充值大小变化很大的 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 个块。
所以我的问题是:解决这个问题的最佳方法是什么?我正在考虑的一些方法,但仍然不确定:
- 使用
ChronicleMapBuilder.maxChunksPerEntry
或ChronicleMapBuilder.actualChunkSize
。那就是说,我如何确定性地弄清楚应该设置什么?另外,如果设置得太高,这可能会导致大量碎片和性能下降,对吗?
- 有一个 "max collection size" 并将非常大的集合分成许多较小的集合,相应地设置密钥。例如,如果我的密钥是
XYZ
,它会产生大小为 10000 的 Set<Integer>
,也许我可以将其拆分为 5 个密钥 XYZ:1
、XYZ: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 已经过时了。
不久前,我问 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 个块。
所以我的问题是:解决这个问题的最佳方法是什么?我正在考虑的一些方法,但仍然不确定:
- 使用
ChronicleMapBuilder.maxChunksPerEntry
或ChronicleMapBuilder.actualChunkSize
。那就是说,我如何确定性地弄清楚应该设置什么?另外,如果设置得太高,这可能会导致大量碎片和性能下降,对吗? - 有一个 "max collection size" 并将非常大的集合分成许多较小的集合,相应地设置密钥。例如,如果我的密钥是
XYZ
,它会产生大小为 10000 的Set<Integer>
,也许我可以将其拆分为 5 个密钥XYZ:1
、XYZ: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 已经过时了。