2017-05-14 93 views
0

我看到迭代在Chronicle Map上的时间非常缓慢 - 在下面的示例中,我的2013 MacbookPro上的1M条目每次迭代93ms。我想知道是否有更好的方法来迭代,或者我做错了什么,或者如果这是预期的?我知道Chronicle Map并未针对迭代进行优化,但几年前的this ticket让我期待更快的迭代时间。玩具下面的例子:Chronicle Map上的迭代非常缓慢

public static void main(String[] args) throws Exception { 
    int numEntries = 1_000_000; 
    int numIterations = 1_000; 
    int avgEntrySize = BitUtil.SIZE_OF_LONG + BitUtil.SIZE_OF_INT; 
    ChronicleMap<IntValue, ByteBuffer> map = ChronicleMap.of(IntValue.class, ByteBuffer.class) 
      .name("test").entries(numEntries).averageValueSize(avgEntrySize) 
      .putReturnsNull(true).create(); 
    IntValue value = Values.newHeapInstance(IntValue.class); 
    ByteBuffer buffer = ByteBuffer.allocate(avgEntrySize); 
    for (int i = 0; i < numEntries; i++) { 
     value.setValue(i); 
     buffer.clear(); 
     buffer.putLong(i); 
     buffer.putInt(i); 
     buffer.flip(); 
     map.put(value, buffer); 
    } 
    System.out.println("Finished insertion"); 

    for (int i = 0; i < numIterations; i++) { 
     map.forEachEntry(entry -> { 
      Data<ByteBuffer> data = entry.value(); 
      ByteBuffer val = data.get(); 
     }); 
    } 
    System.out.println("Finished priming"); 
    long start = System.currentTimeMillis(); 
    for (int i = 0; i < numIterations; i++) { 
     map.forEachEntry(entry -> { 
      Data<ByteBuffer> data = entry.value(); 
      ByteBuffer val = data.get(); 
     }); 
    } 
    System.out.println(
      "Elapsed: " + (System.currentTimeMillis() - start) + " for " + numIterations 
        + " iterations"); 

} 

输出: 完了完了插入 吸 消逝:93327 1000次迭代

+0

如果你需要比你需要有另外的数据结构来索引数据为O(n)操作更好。大型地图的蛮力迭代总是要测试你的硬件。 –

+0

在你提到的票据中,它显示的条目是指容量不是使用的大小,对于大部分为空的地图,它可以加快速度。 –

回答

1

您的结果:每1个百万个密钥93毫秒正好基准的结果这里匹配:http://jetbrains.github.io/xodus/#benchmarks,所以它在预期的球场。 93毫秒/ 1米按键每个按键93纳秒,与“什么”相比,“非常慢”?您的地图包含16 MB有效负载,总堆外大小约为30 MB(仅供参考,您可以通过​​查看),这比消费型笔记本电脑的L3内存容量大得多,因此迭代速度受延迟的主要记忆。 Chronicle Map的迭代主要不是顺序的,所以内存预取不起作用。 I've created an issue about this.

而且你的代码的几个注意事项:

  • 在你的情况下,地图的价值大小是固定的,所以你应该使用constantValueSizeBySample(ByteBuffer.allocate(12)),而不是averageValueSize()。即使地图值大小不恒定,也最好使用averageValue()而不是averageValueSize(),因为您无法确定序列化器有多少字节用于这些值。
  • 对于带有两个字段的value interfaces,您的价值似乎是一个很好的用例。此外,您已经使用值接口作为密钥类型 - IntValue
  • 做基准测试使用JMH
+0

虽然迭代可能会加快,特别是对于大部分为空的地图,人们总是应该期望在每个条目上的蛮力迭代最多也是昂贵的O(n)操作。 –

+0

感谢您的回复!我的意思是说,与3毫秒输入的1.5us相比,速度更慢;链接的代码似乎使用3米条目的地图而不是3米的容量,所以我很惊讶这些数字太遥远了。我错误地阅读了自述文件中的'使用上下文中的条目'部分 - 我期望能够直接读取 - 堆内存,而不是复制整个值,但似乎只适用于值接口。如果我切换到使用值,虽然速度仍然与值的大小成正比,即使它只是在测试v = data.get()但不访问任何字段。 – jlw

+0

单步执行代码我看到它调用((可复制)using).copyFrom(nativeReference);如果我正确读取Generators.java中的copyFromMethod,它实际上会复制整个值,并且当我用jmc查看它时,通过使用initCachedEntryValue通过ValueReader.read可以看到45%的时间转到Heap.copyFrom 。如果这是正确的,你会考虑添加还是已经有迭代的方法,而不需要将值复制到堆中?或者请让我知道,如果我完全脱离了这一切的基础;编年史地图非常新,非常感谢帮助! – jlw