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。
我正在使用 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()
始终 returnsnull
,但您的作品可能是这种情况)
- 使用
确保你指定的是正确的
averageKeySize()
。从这个测试来看,平均密钥大小实际上接近 9 个字节(因为大多数密钥都大于 100 000 000)。但最好尽可能精确,对于此特定测试,这是 8.72,计数为 400 000 000。