簡體   English   中英

為什么JS中的簡單因子算法要比Python或R快得多?

[英]Why simple factorial algorithms in JS are much faster than in Python or R?

為什么JavaScript在這個計算中要快得多?

我用四種簡單的因子算法進行了一些測試:遞歸,尾遞歸, while循環和for循環。 我用R,Python和Javascript進行了測試。

我測量了每個算法計算150個階乘,5000次的時間。 對於RI使用了system.time(replicate()) 對於Python,我使用了time.clock()resource模塊和timeit模塊。 對於JavaScript,我使用了console.time()Date().getMilliseconds()Date().getTime() ,通過終端使用節點運行腳本。

這從來沒有打算比較語言之間的運行時間,但是看看哪種形式(遞歸,尾遞歸,for循環或while循環)對於我正在學習的語言來說更快。 然而,JavaScript算法的性能引起了我的注意。

您可以在此處查看4種不同的因子算法和測量實現:

R階乘算法和性能。

Python階乘算法和性能。

JavaScript因子算法和性能。

在以下示例中,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 s JavaScript:0.004s

為什么JavaScript在這個計算中要快得多?

詳細說來,我只能說R,但這是我的2ct。 也許你可以分析其他語言中發生的事情,然后得出結論。

然而,首先,你的factorialRecursive的R版本不是遞歸的:你調用R的factorial (n - 1)使用$ \\ Gamma $函數。

以下是我的基准測試結果,包括通過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迭代版本之間的差異可能來自於for循環中的n : 1被矢量化的事實。 更進一步,即使用cumprod函數R提供累積產品大大加快了計算速度:與R的基本實現$ \\ Gamma $函數相比,我們在2-3的范圍內(你可能會認為這是作弊)因為cumprod可能后面有一個C函數 - 但是然后R解釋器用C語言編寫,所以區別在這里有點模糊)。

我認為,基本上你在這里付出了很大的代價,因為它是為了交互式使用量身定制的所有安全檢查。 有關Python中的某些相關問題,請參閱“ 為什么Python代碼在函數中運行得更快? ”。

帶回家消息1: R中的遞歸和顯式循環只有在每個函數調用/循環內部的計算足夠復雜時才是合理的選項,因此開銷無關緊要。

帶回家消息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調用所取代,請參閱這些關於遞歸和迭代計算的博客文章( 世界上最差的算法? )和封閉形式計算( Computing Fibonacci)數字使用Binet的公式

我認為主要區別在於Python有bignums而Javascript沒有(它使用雙IEEE754浮點)。

所以你的程序不會計算相同的東西。 使用Python,他們計算所有階乘的數字,JS只有一個粗略的浮點近似,尾數約為15位。

公平地說,您需要找到並使用JS的bignum庫。 看到這個問題

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM