![](/img/trans.png)
[英]When using a HashMap are values and keys guaranteed to be in the same order when iterating?
[英]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.