如果实际数据确实类似于比萨饼、配料和面包皮,i。 e.只有少数不同的浇头/外壳,并且成千上万的比萨饼包含它们中的每一个,我会说在这种情况下拥有适当的多图是过分的,你最好有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 上运行应用程序,则可以通过为每个条目(通过在ChronicleMapBuilder 配置中指定.actualChunkSize())并依赖Linux 的惰性映射内存分配功能(根据需要逐页)。因此,对于每个 UUID 列表,您最多会丢失 4KB 的内存,如果列表有很多 KB 大小,这可能没问题。
另一方面,如果您的列表很长(并且它们是 UUID 列表,即小型结构),并且您总共只有 100 000 个比萨饼,那么您首先不需要 multimap,请参阅开头这个答案。
在 Linux 中内存过度使用和依赖惰性映射内存分配的技巧也适用于值的短列表(集合),但前提是元素本身很大,因此平均总值大小为许多 KB。
当您可以通过任何其他方式避免条目内存重新分配时,碎片也不是问题,即。 e.新的比萨 UUID 会及时添加但也会被删除,因此 topping-to-uuids 列表大小会浮动在某个平均值附近,并且很少会重新分配。
如果在将条目插入 Chronicle Map 后值从未更新(或大小从未改变),则内存碎片永远不会成为问题。
结论
在某些用例中,通过适当的配置,Chronicle Map 可以用作多地图。在其他情况下,Chronicle Map 作为多图本身就效率低下。
重要因素:
- key 的总数 ->
List<Value> multimap 中的条目
- 值的总数
- 密钥大小的平均值和分布
- 不同值大小的平均值和分布
- 值列表大小的平均值和分布
- 值列表在 Chronicle Map 生命周期内的动态(从不更新、仅追加、删除和追加。从列表的开头和中间删除更昂贵。)
- 编年史地图是否被复制