为什么JS中的简单因子算法要比Python或R快得多?

时间:2013-10-07 04:27:57

标签: javascript python r node.js

为什么JavaScript在这个计算中要快得多?

我一直在用四种简单的因子算法进行一些测试:递归,尾递归,while循环和for循环。我用R,Python和Javascript进行了测试。

我测量了每个算法计算150个阶乘,5000次的时间。对于R,我使用了system.time(replicate())。对于Python,我使用了time.clock()resource模块和timeit模块。对于JavaScript,我使用console.time()Date().getMilliseconds()Date().getTime(),通过终端使用节点运行脚本。

这绝不是要比较语言之间的运行时间,而是要查看我正在学习的语言哪种形式(递归,尾递归,for循环或while循环)更快。然而,JavaScript算法的性能引起了我的注意。

您可以在此处查看4种不同的因子算法和测量实现:

R factorial algorithms and performance.

Python factorial algorithms and performance.

JavaScript factorial algorithms and performance.

在以下示例中,f代表循环,w代表while循环。

R的结果是:

Running time of different factorial algorithm implementations, in seconds.
Compute 150 factorial 5000 times:

factorialRecursive()
 user  system elapsed 
0.044   0.001   0.045 

factorialTailRecursive()
 user  system elapsed 
3.409   0.012   3.429 

factorialIterW()
 user  system elapsed 
2.481   0.006   2.488 

factorialIterF()
 user  system elapsed 
0.868   0.002   0.874 

Python的结果是:

Running time of different factorial algorithm implementations.
Uses timeit module, resource module, and a custom performance function.

Compute 150 factorial 5000 times:

factorial_recursive()
timeit:          0.891448974609
custom:          0.87
resource user:   0.870953
resource system: 0.001843

factorial_tail_recursive()
timeit:          1.02211785316
custom:          1.02
resource user:   1.018795
resource system: 0.00131

factorial_iter_w()
timeit:          0.686491012573
custom:          0.68
resource user:   0.687408
resource system: 0.001749

factorial_iter_f()
timeit:          0.563406944275
custom:          0.57
resource user:   0.569383
resource system: 0.001423

JavaScript的结果是:

Running time of different factorial algorithm implementations.
Uses console.time(), Date().getTime() and Date().getMilliseconds()

Compute 150 factorial 5000 times:

factorialRecursive(): 30ms
Using Date().getTime(): 19ms
Using Date().getMilliseconds(): 19ms

factorialTailRecursive(): 44ms
Using Date().getTime(): 44ms
Using Date().getMilliseconds(): 43ms

factorialIterW(): 4ms
Using Date().getTime(): 3ms
Using Date().getMilliseconds(): 3ms

factorialIterF(): 4ms
Using Date().getTime(): 4ms
Using Date().getMilliseconds(): 3ms

如果我理解正确,则无法使用JS代码测量JavaScript中的CPU时间,并且上面使用的方法测量挂钟时间。

JavaScript的挂钟时间测量比Python或R实现快得多。

例如,使用for循环的阶乘算法的挂钟运行时间:     R:0.874s     Python:0.57秒     JavaScript:0.004s

为什么JavaScript在这个计算中要快得多?

2 个答案:

答案 0 :(得分:14)

详细说明我只能说R,但这是我的2ct。也许你可以分析其他语言中发生的事情,然后得出结论。

然而,首先,你的factorialRecursive的R版本不是递归的:你调用使用$ \ Gamma $函数的R factorial (n - 1)

以下是我的基准测试结果,包括通过gamma函数的阶乘和表达迭代计算的更Rish(矢量化)方式:

> factorialCumprod <- function(n) cumprod (seq_len (n))[n]

> microbenchmark(factorial(150), 
                 factorialRecursive(150), factorialTailRecursive(150), 
                 factorialIterF(150), factorialIterW(150), factorialCumprod (150),
                 times = 5000)
Unit: microseconds
                        expr     min      lq   median       uq       max neval
              factorial(150)   1.258   2.026   2.2360   2.5850    55.386  5000
     factorialRecursive(150) 273.014 281.325 285.0265 301.2310  2699.336  5000
 factorialTailRecursive(150) 291.732 301.858 306.4690 323.9295  4958.803  5000
         factorialIterF(150)  71.728  74.941  76.1290  78.7830  2894.819  5000
         factorialIterW(150) 218.118 225.102 228.0360 238.3020 78845.045  5000
       factorialCumprod(150)   3.493   4.959   5.3790   5.9375    65.444  5000

microbenchmark随机化函数调用的顺序。有时,与执行完全相同的函数调用的块相比,这确实有所不同。

我想你在这里可以学到的是,当你选择算法时,你需要考虑语言/语言实现的开发人员的设计决策。

已知R在递归时变慢。我发现一个简单的函数调用没有做任何事情,但返回一个常量已经花费大约750 ns,所以150函数调用将占用递归算法的大约一半的时间。直接调用gamma (150 + 1)代替factorial (150)间接执行此操作会产生类似的差异。如果你想了解更多为什么就是这样,你将不得不问R核心团队。

循环在检查事物上花费了大量的开销。给你一个印象:

> for (i in 1 : 3) {
+    cat (i ," ")
+    i <- 10
+    cat (i ,"\n")
+ }
1  10 
2  10 
3  10 

我认为保存这项工作本质上是矢量化函数的加速来源。

whilefor迭代版本之间的差异可能来自于n : 1循环中的for被矢量化的事实。更进一步,即使用cumprod函数R提供累积产品大大加快了计算速度:与R的基本实现$ \ Gamma $函数相比,我们在2 - 3的范围内(你可能会争辩)这是作弊,因为cumprod可能后面有一个C函数 - 但是然后R解释器是用C语言编写的,所以区别在这里有点模糊)。

我认为,基本上你在这里付出了很大的代价,因为R已经并且必须具备所有安全检查,因为它是针对交互式使用量身定制的。 有关Python中的某些相关问题,请参阅“Why does Python code run faster in a function?”。

带回家消息1:只有当每个函数调用/内部循环中的计算足够复杂时,R中的递归和显式循环都是一个明智的选项,因此开销无关紧要。 / p>

带回家消息2:了解您的数学可以提供很大的帮助:R factorial具有恒定的运行时间(我的笔记本电脑上大约1.8μs):

microbenchmarking factorial vs. cumprod

带回家留言3:然而,这种加速是否至关重要?对于阶乘,可能不是: 图表遍及x的整个范围,其中结果可以由double保持。两个函数的计算都不需要超过ca. 5μs。即使你的“最差”功能也会在500μs内完成。如果要计算大量因子,则使用查找表:170个元素的向量不是那么大。 factorialCumprod为您计算5μs内的整个事物 如果您在计算时碰巧需要更大数字的阶乘,可能您应该努力重新解决问题 - 我总是希望数字问题就在后面(即使您可以使用大整数 - 在R中有包gmp和Rmpfr)


PS:如果您想知道斐波纳契数字不能被类似方便的cumprodcumsum调用所取代,请参阅这些关于递归和迭代计算的博文(The worst algorithm in the world? )和封闭形式计算(Computing Fibonacci numbers using Binet’s formula

答案 1 :(得分:9)

我认为主要区别在于Python有bignums而Javascript没有(它使用双IEEE754浮点)。

所以你的程序不会计算相同的东西。使用Python,他们计算所有阶乘的数字,JS只有一个粗略的浮点近似,尾数约为15位。

公平地说,你需要为JS找出并使用bignum库。请参阅this question