为什么这个js代码这么慢?

时间:2012-07-02 03:12:14

标签: javascript

此代码在Chrome上为3秒,在Firefox上为6秒。 如果我用Java编写代码并在Java 7.0下运行它只需要10ms。 Chrome的JS引擎通常非常快。为什么这么慢? 顺便说一句。此代码仅用于测试。我知道写一个斐波纳契函数不是很实用的方法

fib = function(n) {
  if (n < 2) {
    return n;
  } else {
    return fib(n - 1) + fib(n - 2);
  }
};

console.log(fib(32));

5 个答案:

答案 0 :(得分:6)

这不是javascript的错,而是你的算法。你一遍又一遍地重新计算相同的子问题,当N更大时,它会变得更糟。这是单个呼叫的呼叫图:

                  F(32)
               /         \
            F(31)            F(30)
          /     \           /      \
      F(30)     F(29)     F(29)    F(28)
    /  \      /     \     /   \     |    \
F(29) F(28) F(28) F(27) F(28) F(27) F(27) F(26)

... deeper and deeper

从这棵树中可以看出,你计算了几次斐波纳契数,例如F(28)计算了4次。来自“算法设计手册”一书:

  

此算法花费多少时间来计算F(n)?自F(n + 1)   / F(n)≈φ=(1 + sqrt(5))/2≈1.61803,这意味着F(n)> 1.6 ^ n。既然我们的   递归树只有0和1作为叶子,总结得如此之大   数字意味着我们必须至少有1.6 ^ n个叶子或程序调用!   这个简陋的小程序需要指数时间来运行!

您必须自下而上使用memoization或构建解决方案(即首先是小的子问题)。

此解决方案使用memoization(因此,我们只计算每个Fibonacci数一次):

var cache = {};
function fib(n) {
  if (!cache[n]) {
    cache[n] = (n < 2) ? n : fib(n - 1) + fib(n - 2);
  }
  return cache[n];
}

这个解决了自下而上的问题:

function fib(n) {
  if (n < 2) return n;
  var a = 0, b = 1;
  while (--n) {
    var t = a + b;
    a = b;
    b = t;
  }
  return b;
}

答案 1 :(得分:3)

众所周知,如果天真实施,您在问题中提供的斐波那契函数的实现需要很多步骤。特别是,它需要7,049,155个电话。

然而,使用称为memoization的技术可以大大加快这些算法的速度。如果您看到函数调用fib(32)需要几秒钟,则该函数正在天真地实现。如果它立即返回,则实现很可能使用memoization。

答案 2 :(得分:2)

根据已提供的证据,我得出的结论是:

当代码没有从控制台运行时(比如在我的机器,Sandy Bridge Macbook Air,在55ms内计算它的jsFiddle),JS引擎能够JIT并可能自动记忆算法。

从js控制台运行时,不会发生这种情况。在我的机器上它只慢了10倍:460毫秒。

然后我编辑了代码以寻找F(38),它将时间提高到967ms和9414ms,因此它保持了类似的加速因子。这表明没有执行任何记忆,加速可能是由于JITting。

答案 3 :(得分:1)

只是评论......

函数调用相对昂贵,递归非常昂贵,并且总是慢于使用高效循环的等效函数。例如,以下是IE中递归替代方法的数千倍:

function fib2(n) {
  var arr = [0, 1];
  var len = 2;

  while (len <= n) {
    arr[len] = arr[len-1] + arr[len-2];
    ++len;
  }
  return arr[n];
}

正如其他答案中所指出的那样,OP算法似乎本来就很慢,但我想这不是真正的问题。

答案 4 :(得分:0)

除了@galymzhan推荐的memoization方法之外,您还可以使用其他算法。传统上,第n个斐波纳契数的公式是F(n)= F(n-1)+ F(n-2)。这具有与n成正比的时间复杂度。

Dijkstra提出了一种算法,可以用不到传统公式规定的一半步数推导出斐波那契数。他在撰写EDW #654时概述了这一点。它是:

  1. 对于偶数,F(2n)=(F(n))2 +(F(n + 1))2
  2. 对于奇数,F(2n + 1)=(2F(n)+ F(n + 1))* F(n + 1)或F(2n-1)=(2F(n + 1) - F(n))* F(n)