在编写欧拉问题时,我碰到了我认为奇怪的事情: toString.map方法比toString.toArray.map慢. 这是一个例子: def main(args: Array[String]) { def toDigit(num : Int) = num.toString.map(_ - 48) //2137 ms def toDigitFast
toString.map方法比toString.toArray.map慢.
这是一个例子:
def main(args: Array[String]) { def toDigit(num : Int) = num.toString.map(_ - 48) //2137 ms def toDigitFast(num : Int) = num.toString.toArray.map(_ - 48) //592 ms val startTime = System.currentTimeMillis; (1 to 1200000).map(toDigit) println(System.currentTimeMillis - startTime) }
字符串上的方法映射不应该回退到数组上的映射吗?为什么会出现如此显着的差异? (注意,增加数量甚至会导致非阵列情况下的堆栈溢出).
原版的可能是因为toString.map使用隐式的WrappedString,而toString.toArray.map使用隐式的WrappedArray来解析地图.
让我们看看地图,如TraversableLike中所定义:
def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = { val b = bf(repr) b.sizeHint(this) for (x <- this) b += f(x) b.result }
WrappedString使用StringBuilder作为构建器:
def +=(x: Char): this.type = { append(x); this } def append(x: Any): StringBuilder = { underlying append String.valueOf(x) this }
对Any的String.valueOf调用在Char实例上使用Java Object.toString,可能先获取框.这些额外的操作可能是速度差异的原因,而“阵列”构建器的代码路径应该更短.
这是一个猜测,但必须衡量.
编辑
修改之后,一般的观点仍然存在,但是我引用了错误的含义,因为toDigit方法返回一个Int序列(或类似),而不是我误读的翻译字符串.
toDigit使用LowPriorityImplicits.fallbackStringCanBuildFrom [T]:CanBuildFrom [String,T,immutable.IndexedSeq [T]],T = Int,它只是推荐给一般的IndexedSeq构建器.
toDigitFast使用隐式类型为CanBuildFrom [Array [_],T,Array [T]]的直接数组,无可争议地更快.
为toDigit传递以下CBF明确地使两个方法相同:
object FastStringToArrayBuild { def canBuildFrom[T : ClassManifest] = new CanBuildFrom[String, T, Array[T]] { private def newBuilder = scala.collection.mutable.ArrayBuilder.make() def apply(from: String) = newBuilder def apply() = newBuilder } }