为啥 map.values().stream() 比 Array.stream(array) 慢得多
Posted
技术标签:
【中文标题】为啥 map.values().stream() 比 Array.stream(array) 慢得多【英文标题】:Why is map.values().stream() much slower than Array.stream(array)为什么 map.values().stream() 比 Array.stream(array) 慢得多 【发布时间】:2015-04-26 09:38:53 【问题描述】:为大学第二学期计算机科学创建两个结构来计算文本中的单词。 一种实现使用带有单词对象的数组,将单词保存为字符串,并将其频率保存为 int。 另一种用作HashMap,以Word为键,频率为值。 现在它们是一个函数“totalWords”,它应该返回所有频率的总和。
在 HashMap 变体中:
return _map.values().stream().reduce(0, (a, b) -> a + b);
在数组变体中:
return Arrays.stream(_words)
.map((word) -> word != null ? word.count() : 0)
.reduce(0, (a, b) -> a + b);
我的问题是:在测试文本非常短的 JUnit 测试中,Array 变体确实需要大约 0.001 秒,而地图变体需要 0.040 秒,我不明白为什么地图确实需要这么多时间。 有没有人解释一下,也许有更好的解决方案?
【问题讨论】:
主要原因是您的基准测试存在缺陷。在 Java 中编写这样的微基准非常困难。第二个原因是遍历 HashMap 的条目不如遍历数组快。 关于“更好的解决方案”的旁注:迭代LinkedHashMap
的元素往往比普通的 HashMap
快得多。不过,不如数组快...
【参考方案1】:
其中一个原因是迭代HashMap
可能比Array
慢得多,原因是locality
。现代处理器的计算瓶颈主要是内存访问,这就是为什么使用cache
。 Array
将数据存储在连续的内存块中,这意味着当您将该块内存交换到缓存中时,您更有可能正在使用缓存中的所有内容,或者您得到cache hits
,因此缓存喜欢数据。另一方面,HashMap
的每个元素存储在内存中的不同位置,所以当你遍历HashMap
时,你会得到很多缓存misses
,你最终会交换数据进出缓存一直,这大大减慢了你的程序。
虽然HashMap
的实际实现是优化的,使得内存中的数据聚集在一起,但即使在这种情况下,(@Radiodef)因为HashMap
使用某种linked list
,每个HashMap
的元素包含额外的指针,因此 HashMap
比 Array
消耗更多内存,更多内存意味着更多 cache misses
和更多 page faults
,因此 HashMap
通常比 Array
慢。
【讨论】:
【参考方案2】:HashMap 是一种数据结构,它(基本上)有一个链表数组:
0: [ a ] -> [ b ] -> [ c ]
1: [ ]
2: [ ]
3: [ d ] -> [ e ]
4: [ ]
5: [ f ]
6: [ ]
7: [ ]
链表是键具有相同哈希码的地方(称为“冲突”)。
所以数据结构中存在“漏洞”,而且它比数组更碎片化,因为 HashMap 的每个条目都有一个对象。迭代 HashMap 会比迭代数组产生更多的内存负载。
我也同意 JB Nizet 的观点,您的基准可能存在缺陷。一个好的基准可能仍会显示一个数组的性能更好,但没有那么明显的差异。
【讨论】:
以上是关于为啥 map.values().stream() 比 Array.stream(array) 慢得多的主要内容,如果未能解决你的问题,请参考以下文章
为啥具有短路操作的并行 Java Stream 会评估 Stream 的所有元素,而顺序 Stream 不会?
Java中 map.values转换为list或者string[]
为啥 Java 8 Stream 上没有直接存在 toList() [重复]