为啥 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。现代处理器的计算瓶颈主要是内存访问,这就是为什么使用cacheArray 将数据存储在连续的内存块中,这意味着当您将该块内存交换到缓存中时,您更有可能正在使用缓存中的所有内容,或者您​​得到cache hits,因此缓存喜欢数据。另一方面,HashMap 的每个元素存储在内存中的不同位置,所以当你遍历HashMap 时,你会得到很多缓存misses,你最终会交换数据进出缓存一直,这大大减慢了你的程序。

虽然HashMap的实际实现是优化的,使得内存中的数据聚集在一起,但即使在这种情况下,(@Radiodef)因为HashMap使用某种linked list,每个HashMap 的元素包含额外的指针,因此 HashMapArray 消耗更多内存,更多内存意味着更多 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() [重复]

为啥 Stream.CopyTo 不直接写入文件?

为啥 Image.Save(Stream, ImageFormat) 会抛出异常?

为啥要分块写入 Stream?