据说Scala的For comprehensions实际上非常慢。我得到的原因是由于Java的限制,for comprehensions(如下面使用的“reduce”)需要在每次迭代中生成一个临时对象才能调用传递进来的函数。
这是真的吗?下面的测试似乎证明了这一点,但我不完全理解为什么会这样。
这对于“lambdas”或匿名函数可能是有道理的,但对于非匿名函数则不然。
在我的测试中,我对比了使用list.reduce的for循环(见下面的代码),即使每次迭代调用的是传递给reduce的完全相同的函数,它们也要快两倍以上!
我觉得这非常反直觉(人们可能认为Scala库应该被精心创建以尽可能地优化)。
在我编写的一个测试中,我以五种不同的方式运行了同一个循环(将数字1到一百万相加,无论是否溢出):
1. for循环遍历值数组 2. for循环,但调用函数而不是内联算术 3. for循环,创建一个包含加法函数的对象 4. list.reduce,传递i一个匿名函数 5. list.reduce,传递一个对象成员函数
结果如下: 测试:最小/最大/平均值(毫秒)
可以看出,“for comprehension”版本与“for with new for each instance”版本的顺序相同,这意味着对于匿名和非匿名函数版本,都可能执行“new”操作。
方法:下面的代码(测试调用已删除)被编译成一个单独的.jar文件,以确保所有版本运行相同的库代码。每个迭代中的每个测试都在新的JVM中调用(即对于每个测试使用scala -cp ...),以消除堆大小问题。
这是真的吗?下面的测试似乎证明了这一点,但我不完全理解为什么会这样。
这对于“lambdas”或匿名函数可能是有道理的,但对于非匿名函数则不然。
在我的测试中,我对比了使用list.reduce的for循环(见下面的代码),即使每次迭代调用的是传递给reduce的完全相同的函数,它们也要快两倍以上!
我觉得这非常反直觉(人们可能认为Scala库应该被精心创建以尽可能地优化)。
在我编写的一个测试中,我以五种不同的方式运行了同一个循环(将数字1到一百万相加,无论是否溢出):
1. for循环遍历值数组 2. for循环,但调用函数而不是内联算术 3. for循环,创建一个包含加法函数的对象 4. list.reduce,传递i一个匿名函数 5. list.reduce,传递一个对象成员函数
结果如下: 测试:最小/最大/平均值(毫秒)
1. 27/157/64.78
2. 27/192/65.77 <--- note the similarity between tests 1,2 and 4,5
3. 139/313/202.58
4. 63/342/150.18
5. 63/341/149.99
可以看出,“for comprehension”版本与“for with new for each instance”版本的顺序相同,这意味着对于匿名和非匿名函数版本,都可能执行“new”操作。
方法:下面的代码(测试调用已删除)被编译成一个单独的.jar文件,以确保所有版本运行相同的库代码。每个迭代中的每个测试都在新的JVM中调用(即对于每个测试使用scala -cp ...),以消除堆大小问题。
class t(val i: Int) {
def summit(j: Int) = j + i
}
object bar {
val biglist:List[Int] = (1 to 1000000).toList
def summit(i: Int, j:Int) = i+j
// Simple for loop
def forloop: Int = {
var result: Int = 0
for(i <- biglist) {
result += i
}
result
}
// For loop with a function instead of inline math
def forloop2: Int = {
var result: Int = 0
for(i <- biglist) {
result = summit(result,i)
}
result
}
// for loop with a generated object PER iteration
def forloop3: Int = {
var result: Int = 0
for(i <- biglist) {
val t = new t(result)
result = t.summit(i)
}
result
}
// list.reduce with an anonymous function passed in
def anonymousfunc: Int = {
biglist.reduce((i,j) => {i+j})
}
// list.reduce with a named function
def realfunc: Int = {
biglist.reduce(summit)
}
// test calling code excised for brevity. One example given:
args(0) match {
case "1" => {
val start = System.currentTimeMillis()
forloop
val end = System.currentTimeMillis()
println("for="+(end - start))
}
...
}
.reduce
与“for comprehensions”无关。 - Nikita Volkovreduce
的时间进行比较。 - Richard Sitze