【问题标题】:Recursive GO vs Scala递归 GO 与 Scala
【发布时间】:2014-09-07 20:06:47
【问题描述】:

以下 Scala 代码在 1.5 分钟内完成,而 GO 中的等效代码在 2.5 分钟内完成。
最多 fib(40) 都需要 2 秒。间隙出现在 fib(50)
我的印象是,原生的 GO 应该比 Scala 更快。

斯卡拉

def fib(n:Int):Long = {
    n match {
        case 0 => 0
        case 1 => 1
        case _ => fib(n-1) + fib(n-2)
    }
}

func fib(n int) (ret int) {
    if n > 1 {
        return fib(n-1) + fib(n-2)
    }
    return n
}

Scala 优化?
Golang 限制?

正如“我的另一辆车是 cadr”所说,问题是“为什么 Scala 在这个特定的微基准测试中比 GO 更快?”

忘记斐波那契,假设我确实有一个需要递归的函数。
Scala 在递归情况下更胜一筹吗?

它可能是一个内部编译器实现,甚至是 Scala 特定的优化。
知道的请回答。

在 12 秒内循环运行 15000000000

func fib(n int) (two int) {
    one := 0
    two = 1
    for i := 1; i != n; i++ {
        one, two = two, (one + two)
    }
    return
}

【问题讨论】:

  • 我没有看到任何优化,它是指数级的,递归斐波那契的例子被用来展示如何编写递归函数。似乎,效果是由于一些实现的细微差别,可能是垃圾收集,可能是进程可用的内存(例如,由于某种原因,在 scala 中它足以完成任务而在 go - 不是)。我会在高峰期检查内存消耗。
  • 由于 Scala 是 JIT 编译的,它可以在几次递归调用后做一些临时记忆。我已经让 JVM 在循环展开方面做了一些非常好的优化。
  • 虽然go代码被编译成机器码,但是runtime还是做所有的内存和栈管理,所以和JVM基本一样。我并不奇怪 JVM 比 go 优化得更好。

标签: scala go fibonacci


【解决方案1】:

对于 Go,使用迭代而不是递归。递归可以用显式堆栈的迭代代替。它避免了函数调用和调用堆栈管理的开销。例如,使用迭代并将n 从 50 增加到 1000 几乎不需要时间:

package main

import "fmt"

func fib(n int) (f int64) {
    if n < 0 {
        n = 0
    }
    a, b := int64(0), int64(1)
    for i := 0; i < n; i++ {
        f = a
        a, b = b, a+b
    }
    return
}

func main() {
    n := 1000
    fmt.Println(n, fib(n))
}

输出:

$ time .fib
1000 8261794739546030242
real    0m0.001s
user    0m0.000s
sys 0m0.000s

使用适当的算法。避免指数时间复杂度。当性能很重要时,不要对斐波那契数使用递归。

参考:

Recursive Algorithms in Computer Science Courses: Fibonacci Numbers and Binomial Coefficients

我们观察到分支递归的计算效率低下 几乎所有教科书都没有适当地涵盖功能 计算机科学课程的前三年课程。 斐波那契数和二项式系数经常被用作 分支递归函数的例子。然而,他们的指数 时间复杂度很少有人声称,也从未在 教科书。替代线性时间迭代解决方案很少 提及。我们给出了非常简单的证明,这些递归函数 具有指数时间复杂度。

递归是一种高效的定义和算法技术 只进行一次递归调用,但如果 它进行两个或多个递归调用。因此递归方法是 通常作为概念工具而不是作为一种工具更有用 高效的计算工具。本文提出的证明是 成功教授(超过五年)一年级学生 在渥太华大学。建议将递归作为 解决问题和定义工具将在第二部分中介绍 第一节计算机科学课程。但是,递归编程应该 推迟到课程结束(或者可能更好 第二个计算机科学课程的开始),在迭代之后 程序掌握得很好,堆栈操作很好理解。

【讨论】:

  • 问题不是“如何更快地计算斐波那契”。问题是“为什么 Scala 在这个特定的微基准测试中比 GO 更快”。
  • @Myothercarisacadr:仔细阅读问题。该问题询问从 fib(40) 到 fib(50) 的时间跳跃,从 2 秒到 1.5 到 2.5 分钟。这个答案为 Go 解决了这个问题。对于 Scala,这可能是一个类似的答案。
  • 如果可以的话,你应该总是使用迭代——递归是懒惰的程序员工具。
  • 我不完全同意。您可以通过递归获得相同的复杂性,因为您可以cache the F(N-2) term
【解决方案2】:

Scala 解决方案将消耗堆栈,因为它不是尾递归的(添加发生在递归调用之后),但它根本不应该产生任何垃圾。

对于此代码模式,您使用的任何 Hotspot 编译器(可能是服务器)很可能只是比 Go 编译器更好的编译器。

如果你真的很好奇,可以download a debug build of the JVM, and have it print out the assembly code

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-05
    • 2018-03-08
    • 2018-12-15
    • 1970-01-01
    • 2017-06-18
    • 2018-03-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多