【问题标题】:Why is Array.slice so (shockingly!) slow?为什么 Array.slice 如此(令人震惊!)慢?
【发布时间】:2016-06-22 13:14:41
【问题描述】:

这是我的基准代码:

def bm(duration: Long)(f: => Unit)={
  val end = System.currentTimeMillis + duration
  var count = 0
  while(System.currentTimeMillis < end) { f; count += 1 }
  count
}

val array = new scala.util.Random().alphanumeric.take(1000).toArray

(1 to 20).map { _ => bm(1000) { array.slice(100,200) } }.sum / 20

运行几次,我始终得到大约每秒 150 万片的数字。介于 1.4 和 1.6 之间。

现在,我这样做:

 implicit class FastSlicing(val a: Array[Char]) extends AnyVal {
   def fastSlice(from: Int, until: Int)  = Arrays.copyOfRange(a, from, until)
 }
 (1 to 20).map { _ => bm(1000) { array.fastSlice(100,200) } }.sum / 20

我得到的结果是每秒 16 到 1800 万个切片。 这比 快了 10 倍以上

现在,我知道 scala 为提供功能惯用语和类型安全有时以牺牲性能为代价而做出的权衡取舍的所有常见推理...... 但在这种情况下,我认为他们都无法回答一个简单的问题:为什么ArrayOps.slice 没有以这种方式实现?我意识到,由于 java 处理原始数组的方式,需要多个相同的实现,但这至多是一个小麻烦,并不是一个真正的破坏交易的问题来证明 10 倍的性能损失是合理的。

.slice 只是一个例子,大多数其他数组操作似乎也遇到了同样的问题。为什么一定要这样?

更新,我觉得更令人震惊

val seq = new scala.util.Random().alphanumeric.take(1000).toIndexedSeq
(1 to 20).map { _ => bm(1000) { seq.slice(100,200) } }.sum / 20

这对我来说每秒大约 5-6 百万个切片。但是这个:

import scala.collections.JavaConversions._
(1 to 20).map { _ => bm(1000) { seq.subList(100,200) } }.sum / 20

在 12 到 1500 万之间! 当然,这不是数量级的差异,就像在数组的情况下一样,但是(1)这里没有对原语进行特殊处理,因此仅使用 java 标准工具来实现这将是完全微不足道的,以及(2)集合是不可变的...... 返回对一系列索引的引用有多难???

【问题讨论】:

  • 你已经看过它们背后的代码了吗?
  • @Vale 是的,我有。你读过这个问题吗? ;)
  • 我做了,但找不到你说你做的那一行。我可能不擅长阅读理解。
  • @Vale 实现在这里:github.com/scala/scala/blob/v2.11.5/src/library/scala/…。我觉得文件名真的很讽刺:)
  • 我的猜测是:因为没有人愿意用更有效的实现覆盖ArrayOps 中的slice。您可以提交拉取请求。

标签: arrays performance scala


【解决方案1】:

已在scala 2.12修复。

【讨论】:

  • 是的...由我:D
  • @Zang MingJie 我仍然认为在 slice() 中的 scaladoc 应该是明确的,而不是它声称的“选择”,而是具有所有时间/空间含义的副本。
猜你喜欢
  • 1970-01-01
  • 2017-06-02
  • 2023-04-07
  • 1970-01-01
  • 2021-12-08
  • 1970-01-01
  • 1970-01-01
  • 2019-10-05
  • 1970-01-01
相关资源
最近更新 更多