我的印象是在Scala(2.11.7)中,下面的代码片段会有类似的性能特征,但看起来我错了:
a: IndexedSeq[(Int, Int)]
选项1:
var ans = 0
for {
i <- a.indices
(x1, y1) = a(i)
j <- a.indices drop (i+1)
(x2, y2) = a(j)
if x1 == x2 || y1 == y2
} ans += 1
选项2:
var ans = 0
for (i <- a.indices) {
val (x1, y1) = a(i)
for (j <- a.indices drop (i+1)) {
val (x2, y2) = a(j)
if (x1 == x2 || y1 == y2) ans += 1
}
}
但是,即使是小尺寸(a.length == 100
),第二种方式似乎比第一种方式快5-10倍。此处a
也是IndexedSeq
,因此随机访问几乎不应该那么重要(请参阅下面的f1
vs f2
)。以下是完整的基准测试程序:
import scala.util.Random
object PerfTester extends App {
def f1(a: IndexedSeq[(Int, Int)]) = {
var ans = 0
for {
i <- a.indices
j <- a.indices drop (i+1)
((x1, y1), (x2, y2)) = (a(i), a(j))
if x1 == x2 || y1 == y2
} ans += 1
ans
}
def f2(a: IndexedSeq[(Int, Int)]) = {
var ans = 0
for {
i <- a.indices
(x1, y1) = a(i)
j <- a.indices drop (i+1)
(x2, y2) = a(j)
if x1 == x2 || y1 == y2
} ans += 1
ans
}
def f3(a: IndexedSeq[(Int, Int)]) = {
var ans = 0
for (i <- a.indices) {
val (x1, y1) = a(i)
for (j <- a.indices drop (i+1)) {
val (x2, y2) = a(j)
if (x1 == x2 || y1 == y2) ans += 1
}
}
ans
}
def profile[R](code: => R, t: Long = System.nanoTime()) = (code, (System.nanoTime() - t)/1e6)
val n = 1000
val data = IndexedSeq.fill(n) {
Random.nextInt(100) -> Random.nextInt(100)
}
val (r1, t1) = profile(f1(data))
val (r2, t2) = profile(f2(data))
val (r3, t3) = profile(f3(data))
require(r1 == r2 && r2 == r3)
println(s"f1: $t1 ms")
println(s"f2: $t2 ms")
println(s"f3: $t3 ms")
}
我知道这些测试很容易受到JVM热身,热点优化和排序等的影响,所以我通过随机调用f1
,f2
,f3
的调用和平均来验证这一点许多不同运行的运行时间。
我在codeforces.com上进行编程竞赛时碰到了这个:
答案 0 :(得分:2)
尽管Scala在概念上是等效的,但代码并不是Scala如何编码前一个for循环。特别是,当您编写y = x
时,Scala会执行单独的地图操作,并将答案捆绑为元组。
如果您在命令行询问:
scala -Xprint:typer -e 'for {i <- 1 to 10; j = i*i } println(j)'
你得到(部分):
def main(args: Array[String]): Unit = {
final class $anon extends scala.AnyRef {
def <init>(): <$anon: AnyRef> = {
$anon.super.<init>();
()
};
scala.this.Predef.intWrapper(1).to(10).map[(Int, Int), scala.collection.immutable.IndexedSeq[(Int, Int)]](((i: Int) => {
val j: Int = i.*(i);
scala.Tuple2.apply[Int, Int](i, j)
}))(immutable.this.IndexedSeq.canBuildFrom[(Int, Int)]).foreach[Unit](((x$1: (Int, Int)) => (x$1: (Int, Int) @unchecked) match {
case (_1: Int, _2: Int)(Int, Int)((i @ _), (j @ _)) => scala.this.Predef.println(j)
}))
};
{
new $anon();
()
}
}
你可以看到第7行中的额外地图和第9行创建的元组。然后它只需要在第11行再次提取它。相比于将其插入到foreach函数的闭包中,这是非常昂贵,特别是对于我在本例中使用的整数,因为它们必须装箱。
有人可能会争辩说,现有的方法可能会有所改进,但这就是目前的实施方式。