首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >如何在Scala中优化for-comprehensions和循环?

如何在Scala中优化for-comprehensions和循环?
EN

Stack Overflow用户
提问于 2011-05-27 07:18:02
回答 4查看 20.1K关注 0票数 131

所以Scala应该和Java一样快。我正在重温Scala中的一些Project Euler问题,这些问题我最初是在Java语言中解决的。具体来说,问题5:“能被从1到20的所有数字整除的最小正数是多少?”

这是我的Java解决方案,在我的机器上需要0.7秒才能完成:

代码语言:javascript
复制
public class P005_evenly_divisible implements Runnable{
    final int t = 20;

    public void run() {
        int i = 10;
        while(!isEvenlyDivisible(i, t)){
            i += 2;
        }
        System.out.println(i);
    }

    boolean isEvenlyDivisible(int a, int b){
        for (int i = 2; i <= b; i++) {
            if (a % i != 0) 
                return false;
        }
        return true;
    }

    public static void main(String[] args) {
        new P005_evenly_divisible().run();
    }
}

下面是我“直接翻译”成Scala的代码,它需要103秒(147倍的时间!)

代码语言:javascript
复制
object P005_JavaStyle {
    val t:Int = 20;
    def run {
        var i = 10
        while(!isEvenlyDivisible(i,t))
            i += 2
        println(i)
    }
    def isEvenlyDivisible(a:Int, b:Int):Boolean = {
        for (i <- 2 to b)
            if (a % i != 0)
                return false
        return true
    }
    def main(args : Array[String]) {
        run
    }
}

最后是我对函数式编程的尝试,它需要39秒(55倍的时间)

代码语言:javascript
复制
object P005 extends App{
    def isDivis(x:Int) = (1 to 20) forall {x % _ == 0}
    def find(n:Int):Int = if (isDivis(n)) n else find (n+2)
    println (find (2))
}

在Windows 7 64位平台上使用Scala 2.9.0.1。如何提高性能?我做错了什么吗?还是说Java只是快了很多?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2011-06-16 19:03:15

这种情况下的问题是从for-expression中返回。这反过来会被转换为抛出一个NonLocalReturnException,在封闭的方法中被捕获。优化器可以消除foreach,但还不能消除抛出/捕获。投掷/接球是很昂贵的。但是由于这种嵌套返回在Scala程序中很少见,所以优化器还没有解决这种情况。正在进行改进优化器的工作,希望很快就能解决这个问题。

票数 112
EN

Stack Overflow用户

发布于 2011-06-16 21:02:11

关于理解的答案是正确的,但这并不是全部。您应该注意到,在isEvenlyDivisible中使用return不是免费的。在for内部使用return会迫使scala编译器生成非本地返回(即在其函数外部返回)。

这是通过使用异常退出循环来完成的。如果您构建自己的控件抽象,也会发生同样的情况,例如:

代码语言:javascript
复制
def loop[T](times: Int, default: T)(body: ()=>T) : T = {
    var count = 0
    var result: T = default
    while(count < times) {
        result = body()
        count += 1
    }
    result
}

def foo() : Int= {
    loop(5, 0) {
        println("Hi")
        return 5
    }
}

foo()

这只打印"Hi“一次。

请注意,foo中的return会退出foo (这是您所期望的)。由于括号内的表达式是一个函数式,您可以在loop的签名中看到,这将强制编译器生成非本地返回,即return强制您退出foo,而不仅仅是body

在Java (即JVM)中,实现这种行为的唯一方法是抛出异常。

回到isEvenlyDivisible

代码语言:javascript
复制
def isEvenlyDivisible(a:Int, b:Int):Boolean = {
  for (i <- 2 to b) 
    if (a % i != 0) return false
  return true
}

if (a % i != 0) return false是一个具有返回值的函数,因此每次命中该返回值时,运行时都必须抛出并捕获一个异常,这会导致相当多的GC开销。

票数 8
EN

Stack Overflow用户

发布于 2011-07-06 02:07:19

我只想为那些可能因为这样的问题而对Scala失去信心的人发表评论,这些问题几乎在所有函数式语言的性能中都会出现。如果你在Haskell中优化一个折叠表,你经常需要把它重写成一个递归的尾部调用优化的循环,否则你会遇到性能和内存的问题。

我知道不幸的是,FP还没有优化到我们不需要考虑这样的事情的程度,但这并不是Scala特有的问题。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/6146182

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档