为什么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在这个计算中要快得多?
答案 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
我认为保存这项工作本质上是矢量化函数的加速来源。
while
和for
迭代版本之间的差异可能来自于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):
带回家留言3:然而,这种加速是否至关重要?对于阶乘,可能不是:
图表遍及x的整个范围,其中结果可以由double保持。两个函数的计算都不需要超过ca. 5μs。即使你的“最差”功能也会在500μs内完成。如果要计算大量因子,则使用查找表:170个元素的向量不是那么大。 factorialCumprod
为您计算5μs内的整个事物
如果您在计算时碰巧需要更大数字的阶乘,可能您应该努力重新解决问题 - 我总是希望数字问题就在后面(即使您可以使用大整数 - 在R中有包gmp和Rmpfr)
PS:如果您想知道斐波纳契数字不能被类似方便的cumprod
或cumsum
调用所取代,请参阅这些关于递归和迭代计算的博文(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。