【问题标题】:Compare values in large dataset with ones in small dataset quickly in Java在 Java 中快速比较大数据集中的值和小数据集中的值
【发布时间】:2021-07-03 14:58:37
【问题描述】:

目标

我正在尝试为 Minecraft 创建一个类似于 Skyblock 的岛屿插件,并且我希望能够为每个玩家自己的 3D 岛屿区域计算“岛屿等级”。

情况

我有一个小的hash map,它为我提供了一个十进制值,表示块类型和它们可能处于的状态之间的可能组合的一些子集。

块的数据集很大:大约有 16777216 个块,我需要计算它们每个“值”的总和,如上述地图所示。

“Pseudo-Java”中的一个幼稚(而且非常缓慢)的实现

double total = 0;
for (BlockData block : blocks) {
    for (Entry<Key, Double> entry : map) {
        Key key = entry.getKey();

        // Type check
        if (!key.getString().equals(block.getString()) continue;
        
        // States check (Only ones explicitly defined by entry must match)
        States blockStates = block.getStates();
        States keyStates = key.getStates();
        for (Entry<String, String> state : keyStates) {
            if (!state.getValue().equals(blockStates.get(state.getKey()))
                continue;
        }

        total += entry.getValue();
    }
}

我怎样才能有效地实现水平计算?

附: Delta 编码在这种环境下是不可行的,因为由于 API 限制和混淆,我无法监听块的设置器。

【问题讨论】:

    标签: java dataset comparison


    【解决方案1】:

    我已经设法在第一次运行时将其缩减到不到 1 秒,即使是 6700 万个区块。我想我会在这里分享我的解决方案。

    天真的方法

    我的第一次尝试是简单地在多个线程上运行这种简单的方法(等于或 2 倍于程序可用的逻辑处理器数量)。这很慢,即使处理少量块也需要大约 12 秒左右。

    寻找瓶颈

    我正在使用的 IDE IntelliJ IDEA 预装了一个很棒的工具,称为 Java Flight Recorder。特别是,调用树对于发现和消除瓶颈非常有帮助。

    调用树列出了用于任何特定方法调用的程序时间百分比。这是一个很好的、易于阅读的视图,有助于快速找到瓶颈。

    对于我的具体情况,块数据对象是由无法读取特定状态的外部 API 提供的。最初,我尝试解析块数据的字符串表示,但从性能的角度来看,这是一个糟糕的想法。我的解决方案是为块数据编写一个包装类 QuickBlockData,它具有自定义的 equalshashCode 方法。

    equals 方法访问外部 API 的内部块数据,而不是 API 公开提供的数据。它只比较两个块数据对象中存在的状态键。

    hashCode 方法在不破坏哈希码合约的情况下无法利用任何状态,因此它只返回块数据的类型枚举,而不是查看任何状态。

    FastUtil

    我决定使用 FastUtil 的 Object2DoubleOpenHashMap 作为我的值映射,因为它最接近地代表了我想要拥有的数据结构,并且还提供了相对于 Java 自己的哈希映射实现的合理性能改进。

    我没有直接使用Object2DoubleOpenHashMap,而是在ValueMap 类中进行了扩展,这允许我在getter 中运行一些先发制人的检查,例如根据HashSet 中包含的类型检查输入的类型地图。

    并行化和块

    使用提供块数据的相同 API,可以异步加载 16x16 列的块、块,然后单独解析它们。这非常有用,因为这意味着我可以在加载每个块时对其进行处理。

    API 还提供了块快照,它们是线程安全的对象,在创建块时提供了一些关于块的数据。至关重要的是,他们提供了一种在块内的特定块列中查找最高块的方法,这是一种减少我需要处理的块的简单方法。

    总结

    我用过:

    • 块数据的包装类,具有执行我想要的高性能 equals 方法(尽管它迫使我使用糟糕的哈希码)
    • 一个 ValueMap 类,它扩展了 it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap,允许我快速获取一些块数据的值,具有可自定义的默认值,并具有对块类型的抢先检查。
    • 由外部 API 提供并行计算和分块,使用原子双精度来计算区域的总值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-06-05
      • 2021-09-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多