Chronicle Map 在超过 2 亿个条目时显着变慢

Chronicle Map slows down significantly with more than 200m entries

我正在使用 Chronicle Map 临时存储/查找大量 KV 对(实际上是数十亿)。我不需要持久性或复制,我使用的是内存映射文件,而不是纯堆外内存。平均密钥长度为 8 个字节。

对于较小的数据集 - 最多 2 亿个条目 - 我得到每秒大约 100 万个条目的吞吐量,即创建条目大约需要 200 秒,这很惊人,但是通过 4 亿个条目,地图速度变慢了显着下降,创建它们需要 1500 秒。

我在 Mac OSX/16GB Quad Core/500GB SSD 和 Proliant G6 服务器 运行ning Linux 上进行了 运行 测试,其中 8 cores/64GB ram/300GB 突袭 1(不是 SSD)。两个平台上表现出相同的行为。

如果有帮助,这里是地图设置:

    try {
        f = File.createTempFile(name, ".map");
        catalog = ChronicleMapBuilder
                .of(String.class, Long.class)
                .entries(size)
                .averageKeySize(8)
                .createPersistedTo(f);
    } catch (IOException ioe) {
        // blah
    }

还有一个简单的编写器测试:

    long now = -System.currentTimeMillis();
    long count = 400_000_000L;

    for (long i = 0; i < count; i++) {
        catalog.put(Long.toString(i), i);
        if ((i % 1_000_000) == 0) {
            System.out.println(i + ": " + (now + System.currentTimeMillis()));
        }
    }
    System.out.println(count + ": " + (now + System.currentTimeMillis()));
    catalog.close();

所以我的问题是 - 是否可以进行某种调整来改善这一点,例如更改段数,使用不同的键类型(例如 CharSequence),或者这只是 OS 分页如此大文件的人工产物?

有几件事可能会有所帮助:

  • 确保您使用最新的 Chronicle Map 版本(目前是 3.3.0-beta,下一个 3.4.0-beta 几天后发布)

  • 确实使用 garbage-free 技术,即使对于这样的测试,这也可能很重要,因为垃圾收集可能会启动:

    • 使用 CharSequence 作为键类型,使用 LongValue 作为值类型。
    • 简单的测试代码可能看起来像

      public class VinceTest {
          public static void main(String[] args) throws IOException {
              long count = 400_000_000L;
              File f = File.createTempFile("vince", ".map");
              f.deleteOnExit();
              try (ChronicleMap<CharSequence, LongValue> catalog = ChronicleMap
                      .of(CharSequence.class, LongValue.class)
                      .entries(count)
                      .averageKeySize(8.72)
                      .putReturnsNull(true)
                      .createPersistedTo(f)) {
      
                  long prev = System.currentTimeMillis();
      
                  StringBuilder key = new StringBuilder();
                  LongValue value = Values.newHeapInstance(LongValue.class);
      
                  for (long i = 1; i <= count; i++) {
                      key.setLength(0);
                      key.append(i);
                      value.setValue(i);
                      catalog.put(key, value);
                      if ((i % 1_000_000) == 0) {
                          long now = System.currentTimeMillis();
                          System.out.printf("Average ns to insert per mi #%d: %d\n",
                                  (i / 1_000_000), now - prev);
                          prev = now;
                      }
                  }
                  System.out.println("file size " + MEGABYTES.convert(f.length(), BYTES) + " MB");
              }
          }
      }
      
    • 从上面的源代码中,请注意 putReturnsNull(true) 的用法,以避免在返回值时意外产生垃圾(尽管此测试不是这种情况,因为所有键都是唯一的并且 put() 始终 returns null,但您的作品可能是这种情况)

  • 确保你指定的是正确的averageKeySize()。从这个测试来看,平均密钥大小实际上接近 9 个字节(因为大多数密钥都大于 100 000 000)。但最好尽可能精确,对于此特定测试,这是 8.72,计数为 400 000 000。