繁体   English   中英

为什么使用相同的HashMap迭代器读取密钥时,Java HashMap获取(密钥)的速度比使用Set的迭代器读取密钥要快?

[英]Why Java HashMap get(key) works faster when keys are read using same HashMap's Iterator than when keys are read using a Set's Iterator?

对于HashMap <Integer,Integer>,在插入10000000个唯一随机值后。 我使用hashmap的keySet()执行get(),如下面的代码片段所示:

HashMap<Integer, Integer> hashmap = 
                        new HashMap<Integer, Integer>(10000000, 0.99f);

// ... Code to put unique 10000000 associations into the hashmap ...

int iteration = 100;
long startTime, totalTime = 0;

while(iteration > 0) {
    for(Integer key: hashmap.keySet()) {
       startTime = System.currentTimeMillis();
       hashmap.get(key);
       totalTime += (System.currentTimeMillis() - startTime);
    }
    iteration--;
}
System.out.println(totalTime/100 + " ms");

运行上面的代码,我得到: 225毫秒

现在,如果我将上面的代码改为使用set,就像在下面的代码片段中一样:

Set<Integer> set = new HashSet<Integer>(hashmap.keySet());
while(iteration > 0) {
    for(Integer key: set) {
       startTime = System.currentTimeMillis();
       hashmap.get(key);
       totalTime += (System.currentTimeMillis() - startTime);
    }
    iteration--;
}
System.out.println(totalTime/100 + " ms");

运行此代码后,我得到: 414毫秒

为什么这种性能差异?

PS:我使用了以下JVM参数:

-Xms2048m -Xmx4096m -XX:MaxPermSize=256m

当您读取大型数据结构(大于32 KB)时,您如何阅读该数据结构会影响性能。

这些是您缓存的典型大小和速度。

L1:   32 KB, 4 clock cycles.
L2:  256 KB, 11 clock cycles.
L3: 3-30 MB, 40-75 clock cycles.
Main memory: up to 2TB, 200-500 clock cycles.

这意味着缓存局部性非常重要。 也就是说,如果你正在读取L1中的某些东西,那么它比从L3读取的速度快20倍。

在您的情况下,您正在使用哈希数据结构。 这是为随机访问和随机排列而设计的,遗憾的是它具有非常差的可缓存性。 随机访问内存,它可能在较慢的内存区域。

但是,这是一个例外。 如果您多次访问相同的数据,例如从迭代器获取一个键,或者您正在按顺序扫描一个集合,例如这就是迭代器对HashMap所做的事情(而不是TreeMap)它更有可能您将访问的下一条数据位于同一缓存行(每个缓存行长度为64字节)或下一行。 这些类型的访问执行更好,因为CPU被设计为非常快速地执行向量操作。

BTW你的工作集就是一组键,如果你的值是不同的对象,我希望你实际看这些对象时会慢得多(因为这会增加工作集的大小以及缓存需要多少内存)它)

毫秒精度不足以测量单个get()。 读取循环开始时的时间,并在循环结束时 - 不要尝试在内部的部分增加它,因为这样做会导致大量潜在的准确性错误,淹没任何实际结果。

确保在没有执行计时的情况下运行循环50次(以预热JVM,确保编译所有内容等),然后再次运行它以计算整个循环过程:

Set<Integer> set = new HashSet<Integer>(hashmap.keySet());
startTime = System.currentTimeMillis();
while(iteration > 0) {
    for(Integer key: set) {
       hashmap.get(key);
    }
    iteration--;
}
totalTime = (System.currentTimeMillis() - startTime);
System.out.println(totalTime + " ms");

当你按迭代划分时,你的代码是如何得不到除以0的错误的?

这个

   startTime = System.currentTimeMillis();
   hashmap.get(key);
   totalTime += (System.currentTimeMillis() - startTime);

微观标记是一种荒谬的尝试。 它使用currentTimeMillis()精度为1 ms,实际精度高于10 ms,以测量纳秒操作。 即使是nanoTime本身也无济于事,因为它的准确度通常只有nanoTime微秒。

此外,代码不执行任何预热。

如果你想测量像单个map#get call的表现那样难以捉摸的东西,你应该更好地使用一个合适的微基准测试工具。

找出这两个类的性能的逻辑是不正确的。

测量在键集上完成迭代所花费的时间(最好以纳秒精度),而不是为每次调用get方法测量它。 为了证明你的事实,结果应该是一致的。只有这可以证明你的事实。

此外,性能在很大程度上取决于JVM和GC配置。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM