为什么JS中的简单因子algorithm比Python或R更快?

为什么JavaScript在这个计算中速度更快?

我已经用四个简单的因子algorithm进行了一些testing:recursion,尾recursion, while循环和for循环。 我已经在R,Python和Javascript中进行了testing。

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

这并不是为了比较语言之间的运行时间,而是为了了解我正在学习的语言,哪种forms(recursion,尾recursion,循环或while循环)更快。 不过,JavaScriptalgorithm的性能引起了我的注意。

您可以在这里看到4种不同的因子algorithm和测量实现:

R因子algorithm和性能。

Python因子algorithm和性能。

JavaScript因子algorithm和性能。

在下面的例子中,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循环的阶乘algorithm的挂钟运行时间:R:0.874s Python:0.57 s JavaScript:0.004s

为什么JavaScript在这个计算中速度更快?

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

首先,你的R版本的factorialRecursive不是recursion的:你调用R的factorial (n - 1) ,它使用$ \ Gamma $函数。

这里是我的基准testing结果,其中包括通过伽玛函数的阶乘和expression迭代计算的更多Rish(vector化)方式:

 > 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随机化函数调用的顺序。 与执行完全相同的函数调用的块相比,有时候这会有所作为。

我想你可以在这里学到的是,当你select你的algorithm时,你需要考虑语言/语言实现的开发者的devise决定。

R在recursion中被认为是缓慢的。 我发现一个简单的函数调用没有做任何事情,但返回一个常量已经花了大约750纳秒,所以调用150个函数的时间大约是recursionalgorithm的一半时间。 直接调用gamma (150 + 1)而不是通过factorial (150)间接factorial (150)间接进行此操作会产生类似的差异。 如果你想知道更多为什么这样,你将不得不问R核心团队。

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

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

我认为保存这个工作本质上是向量化函数的加速来自的地方。

while和迭代版本之间的区别可能来自于for循环中的n : 1是vector化的。 更进一步,即使用cumprod函数R提供的累计产品大大加快了计算:我们在2 – 3的因素相比,R的基本实现$ \ Gamma $函数(你可能会认为这是作弊因为cumprod可能有一个C函数,但是R解释器是用C编写的,所以这里的区别有点模糊)。

我想基本上你是在这里为了所有安全检查付出沉重的代价,因为它是为交互式使用量身定制的。 有关Python中相关的问题,请参阅“ 为什么Python代码在函数中运行得更快?

回家的消息1: R中的recursion循环和显式循环都是一个明智的select,只有当循环中的每个函数调用中的计算足够复杂时,开销并不重要。

把家信息2:知道你的math可以帮助很大:R的factorial有恒定的运行时间(我的笔记本电脑约1.8微秒):

microbenchmarking factorial与cumprod

回家信息3:然而,这是否加速问题呢? 对于阶乘,可能不是:graphics遍历x的整个范围,结果可以保持一倍。 这两个函数的计算不超过约。 5μs。 即使你的“最差”的function在500微秒。 如果你有大量的阶乘计算,你可以使用一个查找表:一个170个元素的vector不是那么大。 factorialCumprod为您计算5μs内的整个事情。
如果你碰巧需要更大的数字进行计算,那么可能你应该努力重新expression这个问题 – 我反正会指望数字问题就在这个angular落(即使你可以使用大整数 – 在R中有包gmp和Rmpfr)


PS:如果您想知道斐波纳契数字无法被同样方便的cumprodcumsum调用替代,请参阅这些关于recursion和迭代计算( 世界上最糟糕的algorithm? )和封闭forms计算( 计算斐波纳契数字使用Binet的公式 )

我相信主要区别在于Python有bignums而Javascript不(它使用双IEEE754浮点)。

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

公平地说,你需要找出并使用一个JS的bignum库。 看到这个问题 。