如果实际数据确实类似于比萨饼、浇头和外壳,即只有少数不同的浇头/外壳,并且成千上万的比萨饼包含它们中的每一个,我会说对于这种情况来说,拥有一个适当的多图是矫枉过正的,你最好有pepperoni_pizzas.dat
, onions_pizzas.dat
, ... 具有 UUID 的不同可附加共享列表,您可以使用Chronicle Queue方便地从多个进程访问和更新它们。
如果有 10s-100s 的数千个浇头/外壳,平均只有 10s-100s 的比萨饼有特定的浇头,那么您确实应该使用 multimap。
从本质上讲,Chronicle-Maps-as-multimaps 存在 3 种“问题”:
每个查询上的过多垃圾分配
如果您在没有指定自定义值序列化器的情况下创建具有值List<UUID>
或Set<UUID>
值类型的 Chronicle Map,它会起作用,但效率会非常低,因为它将默认使用内置 Java 序列化来序列化和反序列化每个请求的整个值集合,既不重用集合堆对象,也不重用UUID
元素的单个堆对象。因此,对 ChronicleMap 的每个请求都会产生大量垃圾。
解决方案
但是,如果您将值序列化程序指定为ListMarshaller
或SetMarshaller
(或您可以基于ListMarshaller
和SetMarshaller
实现编写的自定义集合编组器)与可重用的 UUID 堆对象一起指定,它将解决这个垃圾问题:
ListMarshaller<ReusableUuid> valueMarshaller = ListMarshaller.of(
ReusableUuidReader.INSTANCE, ReusableUuidWriter.INSTANCE);
List<ReusableUuid> averageValue = Stream
.generate(() -> ReusableUuid.random())
.limit(averagePizzasForTopping)
.collect(Collectors.toList());
ChronicleMap<Topping, List<ReusableUuid>> map = ChronicleMap
.of(Topping.class, (Class<List<ReusableUuid>>) (Class) List.class)
.averageKey(pepperoni)
.valueMarshaller(valueMarshaller)
.averageValue(averageValue)
.entries(numberOfToppings)
.createPersistedTo(new File("toppings_to_pizza_ids.dat"));
低效的价值更新和复制
当您将另一个披萨 UUID 附加到 100 个 UUID 的列表中,并将新值插入 Chronicle Map 时,Chronicle Map 将再次重写整个列表,而不是将一个 UUID 附加到堆外内存块的末尾。如果你使用复制,它会将 100 个 UUID 的整个列表作为更新值发送到其他节点,而不是只发送一个添加的 UUID。
两者(值更新和复制)都可以通过可怕的 hack 进行优化,但它需要非常深入的 Chronicle Map 实现知识,并且非常脆弱。
Chronicle-Map的记忆碎片
如果您计划在数据存储生命周期内添加新的比萨饼,最初为整体分配的内存区域将变得太小而无法容纳具有更多 UUID 的新值,因此将重新分配内存区域(可能为每个 UUID 列表分配多次)。Chronicle Map 的数据结构设计意味着简化的内存分配方案,如果条目被多次重新分配,它会严重地受到碎片的影响。
如果列表中有很多 UUID,并且在 Linux 上运行应用程序,则可以通过为每个条目(通过.actualChunkSize()
在ChronicleMapBuilder
配置中指定) 并依赖于 Linux 的惰性映射内存分配特性(根据需要逐页)。因此,对于每个 UUID 列表,您最多会丢失 4KB 的内存,如果列表有很多 KB 大小,这可能没问题。
另一方面,如果您的列表很长(并且它们是 UUID 列表,即小型结构),并且您总共只有 100 000 个比萨饼,那么您首先不需要 multimap,请参阅此答案的开头.
Linux 中内存过度使用和依赖惰性映射内存分配的技巧也适用于值的短列表(集合),但前提是元素本身很大,因此平均总值大小为许多 KB。
当您可以通过任何其他方式避免条目内存重新分配时,碎片也不是问题,即新的比萨 UUID 会及时添加但也会被删除,因此 topping-to-uuids 列表大小浮动在某个平均值附近并且很少重新分配击中。
如果在将条目插入 Chronicle Map 后值从未更新(或大小从未改变),则内存碎片永远不会成为问题。
结论
在某些用例和适当的配置下,Chronicle Map 可以用作多地图。在其他情况下,Chronicle Map 作为多图本质上是低效的。
重要的因素:
- 多图中的键总数 ->
List<Value>
条目
- 值的总数
- 密钥大小的平均值和分布
- 不同值大小的平均值和分布
- 价值列表大小的平均值和分布
- 值列表在 Chronicle Map 生命周期内的动态(从不更新、仅追加、删除和追加。从列表的开头和中间删除更昂贵。)
- 如果编年史地图被复制,或者没有