gpt4 book ai didi

java - ChronicleMap 中的多重 map

转载 作者:塔克拉玛干 更新时间:2023-11-03 03:31:38 34 4
gpt4 key购买 nike

ChronicleMap's GitHub上肯定有免责声明关于 ChronicleMap 中的 Multimaps:

Chronicle Map is not...

... No secondary indexes.

A multimap. Using a ChronicleMap<K, Collection<V>> as multimap is technically possible, but often leads to problems...


不幸的是,这是我的用例之一,为此使用堆外存储(使用 ChronicleMap)肯定是最简单的方法。
让我试着用比萨饼来解释我的问题。我有 100,000 个不同的比萨饼。每个披萨都有一个 ID 和许多不同的配料和 shell 。我有三种访问模式:
  • 凭身份证把披萨给我。
  • 给我所有有特定配料的比萨饼。
  • 给我所有有特殊 shell 的比萨饼。

  • 我可以使用 ChronicleMap<UUID,Pizza> 轻松存放比萨饼.但这只是一种访问模式。我不想遍历每个比萨饼以找到具有匹配顶部或 shell 的比萨饼。所以,我想存储类似 ChronicleMap<Topping,Collection<UUID>> 的东西和 ChronicleMap<Crust,Collection<UUID>> .
    然后,如果有人要我所有的意大利辣香肠比萨,我会在顶部 ChronicleMap 中查找匹配比萨的 UUID,然后在主比萨 map 中查找。
    但是上面引用的文档让我感到害怕。有谁知道这种事情经常导致的这些“问题”可能是什么?为什么我不应该这样做,即使它似乎对我有用?它是否与 ChronicleMap 存储序列化对象的方式有关,特别是集合?
    针对潜在问题的一些附加说明:
  • 我们可能会在稍后添加比萨饼,这也需要更新集合。
  • 许多进程都在尝试执行这些操作,因此需要通过 ChronicleMap 而不是基本的 ConcurrentMap 共享 map 。
  • 最佳答案

    如果实际数据确实类似于比萨饼、浇头和面包皮,我。 e.只有少数不同的浇头/ shell ,而数千个比萨饼包含它们中的每一个,我会说在这种情况下拥有适当的多映射是过度的,你最好有 pepperoni_pizzas.datonions_pizzas.dat 、...不同的可附加共享列表UUID,您可以使用 Chronicle Queue 方便地从多个进程访问和更新它们。

    如果有 10 到 100 种配料/ shell ,平均只有 10 到 100 种比萨饼具有特定配料,那么您确实应该使用多图。

    本质上,Chronicle-Maps-as-multimaps 有 3 种“问题”:

    每个查询的垃圾分配过多

    如果您使用 List<UUID>Set<UUID> 类型的值创建一个 Chronicle Map 而不指定自定义值序列化器,它会工作,但效率会非常低,因为它将默认使用内置 Java 序列化来序列化和反序列化每个值集合请求,既不重用集合堆对象,也不重用元素的单个 UUID 堆对象。因此,对 ChronicleMap 的每个请求都会产生大量垃圾。

    解决方案
    但是,如果您将值序列化程序指定为 ListMarshaller SetMarshaller (或您的自定义集合编码器,您可以根据 ListMarshallerSetMarshaller 实现编写)结合可重用的 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"));

    低效的值(value)更新和复制

    当您将另一个披萨 UUID 附加到 100 个 UUID 的列表中,并将新值插入回 Chronicle Map 时,Chronicle Map 将再次重写整个列表,而不是将一个 UUID 附加到堆外内存块的末尾。如果您使用复制,它会将 100 个 UUID 的整个列表作为更新值发送到其他节点,而不是仅发送一个添加的 UUID。

    两者(值更新和复制)都可以通过可怕的黑客进行优化,但它需要非常深入的 Chronicle Map 实现知识,并且非常脆弱。

    Chronicle-Map 内存的碎片化

    如果您计划在数据存储生命周期内添加新的比萨饼,最初为整体分配的内存区域将变得太小,无法容纳具有更多 UUID 的新值,因此将重新分配内存区域(每个 UUID 列表可能会重新分配多次)。 Chronicle Map 的数据结构设计意味着简化的内存分配方案,如果条目被多次重新分配,则会严重受到碎片化的影响。

    如果列表中有很多 UUID,并且在 Linux 上运行应用程序,则可以通过为每个条目预先分配大量内存(比任何列表实际需要的多)(通过在 .actualChunkSize() 配置)并依赖 Linux 的延迟映射内存分配功能(根据需要逐页)。因此,对于每个 UUID 列表,您最多会丢失 4KB 的内存,如果列表有很多 KB 大小,这可能没问题。

    另一方面,如果您的列表很长(并且它们是 UUID 列表,即小结构),并且您总共只有 100 000 个比萨饼,那么您首先不需要 multimap,请参阅此答案的开头.

    在 Linux 中内存过量使用和依赖延迟映射内存分配的技巧也适用于值的短列表(集合),但前提是元素本身很大,因此平均总值大小为许多 KB。

    当您可以通过任何其他方式避免条目内存重新分配时,碎片也不再是一个问题,即。 e.新的披萨 UUID 会及时添加但也会被删除,因此从顶部到 uuid 的列表大小会 float 在某个平均值左右,并且很少会发生重新分配。

    如果在条目插入 Chronicle Map 后值从不更新(或从不改变大小),则内存碎片永远不会成为问题。

    结论

    在某些用例和适当的配置下,Chronicle Map 可以很好地用作多 map 。在其他情况下,作为多图的 Chronicle Map 本质上是低效的。

    重要的因素:
  • 键的总数 -> ChronicleMapBuilder 多映射中的条目
  • 值的总数
  • key 大小的平均值和分布
  • 不同值大小的平均值和分布
  • 值列表大小的平均值和分布
  • 值列表在 Chronicle Map 生命周期内的动态(从不更新,仅附加,删除和附加。从列表的开头和中间删除更昂贵。)
  • Chronicle Map 是否被复制
  • 关于java - ChronicleMap 中的多重 map ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36483569/

    34 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com