Javascript 使用ProjectEuler2编写缓慢的Haskell和Python代码

Javascript 使用ProjectEuler2编写缓慢的Haskell和Python代码,javascript,c#,python,haskell,f#,Javascript,C#,Python,Haskell,F#,我和Haskell一起练习了一段时间,以了解这门语言,它太神奇了,所以我去了euler项目,做了第二题,花了相当长的时间~30-40秒,我不知道到底要完成多长时间。我想知道为什么花了这么长时间,所以我用F、C Javascript和Python尝试了同样的方法。F和C与javascript一样需要几毫秒才能完成,但python比Haskell花的时间要多。为什么呢?这些是我的实现 哈斯克尔 fib 0=1 fib 1=1 fibn=fibn-1+fibn-2 genFibs n最大值= 如果fi

我和Haskell一起练习了一段时间,以了解这门语言,它太神奇了,所以我去了euler项目,做了第二题,花了相当长的时间~30-40秒,我不知道到底要完成多长时间。我想知道为什么花了这么长时间,所以我用F、C Javascript和Python尝试了同样的方法。F和C与javascript一样需要几毫秒才能完成,但python比Haskell花的时间要多。为什么呢?这些是我的实现

哈斯克尔

fib 0=1 fib 1=1 fibn=fibn-1+fibn-2 genFibs n最大值= 如果fib n 设rec fib n= 匹配 |当n<2->1时为n |n->fib n-1+fib n-2 让rec genFibos n max= 将fibnfibn::genFibos n+1最大值 |假->[] genFibos 1 4000000 |>Seq.where fun->n%2=0 |>序号总和 C

静态整型纤维 { 返回n<2-1:Fibn-1+Fibn-2; } 公共静态整流罩 { var n=1; var totalSum=0; var-num=1; whilenum<4000000 { num=Fibn; 如果num%2==0 totalSum+=num; n+=1; } 返回总和; } 蟒蛇

knownVals={} def fibn: 如果n不在knownVals中: 如果n<2,则knownVals[n]=1,否则fibn-1+fibn-2 返回已知值[n] n=1 仍然计数=真 总和=0 仍在计数时: num=fibn 如果num>4000000: 仍然计数=假 其他: 如果num%2==0: 总和+=num n+=1 打印总数 Javascript

(function () {

function fib(n) {
    return n < 2 ? 1 : fib(n - 1) + fib(n - 2);
}

var totalSum = 0;
var num = 1;
var n = 1;

while(num < 4000000)
{
    num = fib(n);
    if (num % 2 == 0) totalSum += num;
    n += 1;
}

alert(totalSum);

})();
那么,有人能解释为什么Haskell和Python在这方面很慢,而F和C在这方面很快,以及我如何增强它吗?任何帮助都将不胜感激

编辑:Haskell代码已更正,Python更好地使用备忘录实现

尝试将其备忘录化

known_fibs={}
def fib(n):
    if n not in known_fibs:
        known_fibs[n] = 1 if n < 2 else fib(n-1) + fib(n-2)
    return known_fibs[n]

你想知道所有的时间都在哪里,对吗?不要把它看作是测量时间。可以将其视为查找大部分时间都在堆栈上的代码行,而不考虑总时间。下面是一个例子:


许多探查器落入gprof陷阱,包括忽略阻塞时间、认为代码行无关紧要、认为自我时间无关紧要,以及认为测量必须准确。Haskell实现完全错误:它永远不会完成,因为:

fibn=fibn-1+fibn-2 同:

fibn=fibn-1+fibn-2 这是有分歧的

正确的实施:

fib 0=1 fib 1=1 fibn=fibn-1+fibn-2 genFibs n maxVal |fib n $time./fibo 4613732 实0m1.334s 用户0m1.324s 系统0m0.009s python解决方案的问题在于解释器没有为双重递归调用引入任何类型的优化或记忆。这意味着算法需要指数时间来计算答案。很容易想出多项式时间算法:

def fibon: 上一个,当前=0,1 对于我在兰根: 上一个,当前=当前,上一个+当前 返回电流 其结果是:

在[8]中:%%timeit …:tot=0 …:n=0 …:虽然正确: …:num=fibon …:如果数值>4000000: …:休息 …:elif num%2==0: …:tot+=num …:n+=1 ...: 10000个回路,最好为3个:每个回路54.8µs
我对F和C的速度的猜测是编译器引入了某种形式的记忆以避免指数增长。尽管问题可能仍然太小,无法注意到函数调用的指数增长。如果你试图将4000000增加到4000000或更多,你肯定可以检查这是否正确。

斐波那契函数的递归定义是解决这个问题的糟糕方法。下面是一个JavaScript实现,它基本上是即时运行的:

function evenFibSum(limit) {
  var
    pf, f, t, sum;

  for (pf = 1, f = 2, sum = 0; f <= limit; t = f, f += pf, pf = t)
    if (!(f & 1)) sum += f;

  return sum;
}
console.log(evenFibSum(4000000));

在处理整个列表时递归生成每个值的工作太多了,而且迭代计算值非常简单。记住递归定义有帮助,但对我来说它似乎不必要的复杂。

python可能很慢,因为python一般都很慢。@RobertHarvey python没有那么慢。我100%使用它进行大量的数字处理,而且它的功能非常强大。问题在于实现,而不是语言。顺便说一句:您的Haskell实现是完全错误的。它应该是fibn=fibn-1+fibn-2,而不是fibn-1+fibn-2。事实上,您对fib的定义是不同的,即它永远循环,或者直到堆栈溢出。python解决方案在我的机器上运行不到10秒。修正后的Haskell解决方案运行约1秒。这里有些不对劲。@ZaidAjaj您应该确保您已经使用ghc-O2编译了它,以便在我们进行优化时使用它
陆上通信线。使用runhaskell或GHCi运行它将导致执行速度大大降低。另外,这个问题的更快实现是sum$filter,甚至$take,而他在问题中提供的Python解决方案并没有那么慢,除非他有一台非常旧的计算机。它在我的机器上运行3秒钟。这在python上运行得很好…我将尝试为我的机器上的其他实现找到类似的东西,在引入memonization之后,F解决方案需要0.005秒。只有当大部分执行在C实现中进行,而没有在Python和C之间进行大量切换时,Python才会快速运行。@mydogisbox您的评论完全证实了我所说的:如果引入memonization不会改变运行时,则意味着编译器已经引入了它。k*33调用所用的时间不可能与k'*2^32调用naive算法执行的调用次数相同,而不需要记忆。您可以看到python实现中的差异:从10秒到10微秒。另外,这两个实现都是纯python,没有对内置函数的调用,因此您关于切换到C的观点在这种情况下是完全无效的。我的观点是.net没有为您添加momoization,尽管这会很酷。出于某种原因,我没有看到您改进的python实现。当您迭代这些值时,您可以维护前一个值和当前值变量,并完全避免递归。