與Project Euler的速度比較:C vs Python vs Erlang vs Haskell

本文翻譯自:Speed comparison with Project Euler: C vs Python vs Erlang vs Haskell

I have taken Problem #12 from Project Euler as a programming exercise and to compare my (surely not optimal) implementations in C, Python, Erlang and Haskell. 我將Project Euler中的問題#12作爲編程練習並比較了我在C,Python,Erlang和Haskell中的(當然不是最優的)實現。 In order to get some higher execution times, I search for the first triangle number with more than 1000 divisors instead of 500 as stated in the original problem. 爲了獲得更高的執行時間,我搜索第一個三角形數字,其中有超過1000個除數而不是原始問題中所述的500。

The result is the following: 結果如下:

C: C:

lorenzo@enzo:~/erlang$ gcc -lm -o euler12.bin euler12.c
lorenzo@enzo:~/erlang$ time ./euler12.bin
842161320

real    0m11.074s
user    0m11.070s
sys 0m0.000s

Python: 蟒蛇:

lorenzo@enzo:~/erlang$ time ./euler12.py 
842161320

real    1m16.632s
user    1m16.370s
sys 0m0.250s

Python with PyPy: Python與PyPy:

lorenzo@enzo:~/Downloads/pypy-c-jit-43780-b590cf6de419-linux64/bin$ time ./pypy /home/lorenzo/erlang/euler12.py 
842161320

real    0m13.082s
user    0m13.050s
sys 0m0.020s

Erlang: 二郎:

lorenzo@enzo:~/erlang$ erlc euler12.erl 
lorenzo@enzo:~/erlang$ time erl -s euler12 solve
Erlang R13B03 (erts-5.7.4) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.7.4  (abort with ^G)
1> 842161320

real    0m48.259s
user    0m48.070s
sys 0m0.020s

Haskell: 哈斯克爾:

lorenzo@enzo:~/erlang$ ghc euler12.hs -o euler12.hsx
[1 of 1] Compiling Main             ( euler12.hs, euler12.o )
Linking euler12.hsx ...
lorenzo@enzo:~/erlang$ time ./euler12.hsx 
842161320

real    2m37.326s
user    2m37.240s
sys 0m0.080s

Summary: 摘要:

  • C: 100% C:100%
  • Python: 692% (118% with PyPy) Python:692%(使用PyPy時爲118%)
  • Erlang: 436% (135% thanks to RichardC) Erlang:436%(135%歸功於RichardC)
  • Haskell: 1421% 哈斯克爾:1421%

I suppose that C has a big advantage as it uses long for the calculations and not arbitrary length integers as the other three. 我認爲C有一個很大的優勢,因爲它使用long進行計算而不是任意長度整數作爲其他三個。 Also it doesn't need to load a runtime first (Do the others?). 此外,它不需要首先加載運行時(其他人?)。

Question 1: Do Erlang, Python and Haskell lose speed due to using arbitrary length integers or don't they as long as the values are less than MAXINT ? 問題1: Erlang,Python和Haskell是否由於使用任意長度整數而失去速度,或者只要值小於MAXINT就不會失敗?

Question 2: Why is Haskell so slow? 問題2:爲什麼Haskell這麼慢? Is there a compiler flag that turns off the brakes or is it my implementation? 是否有編譯器標誌關閉剎車或是我的實施? (The latter is quite probable as Haskell is a book with seven seals to me.) (後者非常可能,因爲Haskell是一本帶有七個印章的書。)

Question 3: Can you offer me some hints how to optimize these implementations without changing the way I determine the factors? 問題3:您能否提供一些提示,如何優化這些實現而不改變我確定因素的方式? Optimization in any way: nicer, faster, more "native" to the language. 以任何方式進行優化:更好,更快,更“本地”的語言。

EDIT: 編輯:

Question 4: Do my functional implementations permit LCO (last call optimization, aka tail recursion elimination) and hence avoid adding unnecessary frames onto the call stack? 問題4:我的功能實現是否允許LCO(最後調用優化,也就是尾遞歸消除),從而避免在調用堆棧中添加不必要的幀?

I really tried to implement the same algorithm as similar as possible in the four languages, although I have to admit that my Haskell and Erlang knowledge is very limited. 我真的試圖在四種語言中儘可能地實現相同的算法,儘管我必須承認我的Haskell和Erlang知識非常有限。


Source codes used: 使用的源代碼:

#include <stdio.h>
#include <math.h>

int factorCount (long n)
{
    double square = sqrt (n);
    int isquare = (int) square;
    int count = isquare == square ? -1 : 0;
    long candidate;
    for (candidate = 1; candidate <= isquare; candidate ++)
        if (0 == n % candidate) count += 2;
    return count;
}

int main ()
{
    long triangle = 1;
    int index = 1;
    while (factorCount (triangle) < 1001)
    {
        index ++;
        triangle += index;
    }
    printf ("%ld\n", triangle);
}

#! /usr/bin/env python3.2

import math

def factorCount (n):
    square = math.sqrt (n)
    isquare = int (square)
    count = -1 if isquare == square else 0
    for candidate in range (1, isquare + 1):
        if not n % candidate: count += 2
    return count

triangle = 1
index = 1
while factorCount (triangle) < 1001:
    index += 1
    triangle += index

print (triangle)

-module (euler12).
-compile (export_all).

factorCount (Number) -> factorCount (Number, math:sqrt (Number), 1, 0).

factorCount (_, Sqrt, Candidate, Count) when Candidate > Sqrt -> Count;

factorCount (_, Sqrt, Candidate, Count) when Candidate == Sqrt -> Count + 1;

factorCount (Number, Sqrt, Candidate, Count) ->
    case Number rem Candidate of
        0 -> factorCount (Number, Sqrt, Candidate + 1, Count + 2);
        _ -> factorCount (Number, Sqrt, Candidate + 1, Count)
    end.

nextTriangle (Index, Triangle) ->
    Count = factorCount (Triangle),
    if
        Count > 1000 -> Triangle;
        true -> nextTriangle (Index + 1, Triangle + Index + 1)  
    end.

solve () ->
    io:format ("~p~n", [nextTriangle (1, 1) ] ),
    halt (0).

factorCount number = factorCount' number isquare 1 0 - (fromEnum $ square == fromIntegral isquare)
    where square = sqrt $ fromIntegral number
          isquare = floor square

factorCount' number sqrt candidate count
    | fromIntegral candidate > sqrt = count
    | number `mod` candidate == 0 = factorCount' number sqrt (candidate + 1) (count + 2)
    | otherwise = factorCount' number sqrt (candidate + 1) count

nextTriangle index triangle
    | factorCount triangle > 1000 = triangle
    | otherwise = nextTriangle (index + 1) (triangle + index + 1)

main = print $ nextTriangle 1 1

#1樓

參考:https://stackoom.com/question/TDku/與Project-Euler的速度比較-C-vs-Python-vs-Erlang-vs-Haskell


#2樓

With Haskell, you really don't need to think in recursions explicitly. 使用Haskell,您實際上不需要明確地考慮遞歸。

factorCount number = foldr factorCount' 0 [1..isquare] -
                     (fromEnum $ square == fromIntegral isquare)
    where
      square = sqrt $ fromIntegral number
      isquare = floor square
      factorCount' candidate
        | number `rem` candidate == 0 = (2 +)
        | otherwise = id

triangles :: [Int]
triangles = scanl1 (+) [1,2..]

main = print . head $ dropWhile ((< 1001) . factorCount) triangles

In the above code, I have replaced explicit recursions in @Thomas' answer with common list operations. 在上面的代碼中,我用@Thomas的回答用公共列表操作替換了顯式遞歸。 The code still does exactly the same thing without us worrying about tail recursion. 代碼仍然完全相同,沒有我們擔心尾遞歸。 It runs (~ 7.49s ) about 6% slower than the version in @Thomas' answer (~ 7.04s ) on my machine with GHC 7.6.2, while the C version from @Raedwulf runs ~ 3.15s . 在運行GHC 7.6.2的機器上運行(~ 7.49s )比@Thomas的答案(~ 7.04s )慢6%左右 ,而@Raedwulf的C版運行~ 3.15s It seems GHC has improved over the year. GHC似乎在過去一年中有所改善。

PS. PS。 I know it is an old question, and I stumble upon it from google searches (I forgot what I was searching, now...). 我知道這是一個老問題,我從谷歌搜索中偶然發現它(我忘記了我在搜索,現在......)。 Just wanted to comment on the question about LCO and express my feelings about Haskell in general. 只是想對有關LCO的問題發表評論,並表達我對Haskell的總體感受。 I wanted to comment on the top answer, but comments do not allow code blocks. 我想對最佳答案發表評論,但評論不允許代碼塊。


#3樓

Your Haskell implementation could be greatly sped up by using some functions from Haskell packages. 通過使用Haskell包中的一些函數,可以大大加快Haskell的實現。 In this case I used primes, which is just installed with 'cabal install primes' ;) 在這種情況下,我使用了primes,它只是安裝了'cabal install primes';)

import Data.Numbers.Primes
import Data.List

triangleNumbers = scanl1 (+) [1..]
nDivisors n = product $ map ((+1) . length) (group (primeFactors n))
answer = head $ filter ((> 500) . nDivisors) triangleNumbers

main :: IO ()
main = putStrLn $ "First triangle number to have over 500 divisors: " ++ (show answer)

Timings: 時序:

Your original program: 你原來的節目:

PS> measure-command { bin\012_slow.exe }

TotalSeconds      : 16.3807409
TotalMilliseconds : 16380.7409

Improved implementation 改進實施

PS> measure-command { bin\012.exe }

TotalSeconds      : 0.0383436
TotalMilliseconds : 38.3436

As you can see, this one runs in 38 milliseconds on the same machine where yours ran in 16 seconds :) 正如你所看到的,這個在同一臺機器上運行38毫秒,你的運行時間爲16秒:)

Compilation commands: 編譯命令:

ghc -O2 012.hs -o bin\012.exe
ghc -O2 012_slow.hs -o bin\012_slow.exe

#4樓

Question 1: Do Erlang, Python and Haskell lose speed due to using arbitrary length integers or don't they as long as the values are less than MAXINT? 問題1:Erlang,Python和Haskell是否由於使用任意長度整數而失去速度,或者只要值小於MAXINT就不會失敗?

Question one can be answered in the negative for Erlang. 問題一可以回答Erlang的否定。 The last question is answered by using Erlang appropriately, as in: 最後一個問題是通過適當地使用Erlang來回答的,如:

http://bredsaal.dk/learning-erlang-using-projecteuler-net http://bredsaal.dk/learning-erlang-using-projecteuler-net

Since it's faster than your initial C example, I would guess there are numerous problems as others have already covered in detail. 由於它比你最初的C例子快,我猜它會有很多問題,因爲其他人已經詳細介紹了。

This Erlang module executes on a cheap netbook in about 5 seconds ... It uses the network threads model in erlang and, as such demonstrates how to take advantage of the event model. 這個Erlang模塊在大約5秒內在便宜的上網本上執行...它使用erlang中的網絡線程模型,並且因此演示瞭如何利用事件模型。 It could be distributed over many nodes. 它可以分佈在許多節點上。 And it's fast. 它很快。 Not my code. 不是我的代碼。

-module(p12dist).  
-author("Jannich Brendle, [email protected], http://blog.bredsaal.dk").  
-compile(export_all).

server() ->  
  server(1).

server(Number) ->  
  receive {getwork, Worker_PID} -> Worker_PID ! {work,Number,Number+100},  
  server(Number+101);  
  {result,T} -> io:format("The result is: \~w.\~n", [T]);  
  _ -> server(Number)  
  end.

worker(Server_PID) ->  
  Server_PID ! {getwork, self()},  
  receive {work,Start,End} -> solve(Start,End,Server_PID)  
  end,  
  worker(Server_PID).

start() ->  
  Server_PID = spawn(p12dist, server, []),  
  spawn(p12dist, worker, [Server_PID]),  
  spawn(p12dist, worker, [Server_PID]),  
  spawn(p12dist, worker, [Server_PID]),  
  spawn(p12dist, worker, [Server_PID]).

solve(N,End,_) when N =:= End -> no_solution;

solve(N,End,Server_PID) ->  
  T=round(N*(N+1)/2),
  case (divisor(T,round(math:sqrt(T))) > 500) of  
    true ->  
      Server_PID ! {result,T};  
    false ->  
      solve(N+1,End,Server_PID)  
  end.

divisors(N) ->  
  divisor(N,round(math:sqrt(N))).

divisor(_,0) -> 1;  
divisor(N,I) ->  
  case (N rem I) =:= 0 of  
  true ->  
    2+divisor(N,I-1);  
  false ->  
    divisor(N,I-1)  
  end.

The test below took place on an: Intel(R) Atom(TM) CPU N270 @ 1.60GHz 下面的測試發生在:Intel(R)Atom(TM)CPU N270 @ 1.60GHz

~$ time erl -noshell -s p12dist start

The result is: 76576500.

^C

BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
a

real    0m5.510s
user    0m5.836s
sys 0m0.152s

#5樓

Change: case (divisor(T,round(math:sqrt(T))) > 500) of 更改: case (divisor(T,round(math:sqrt(T))) > 500) of

To: case (divisor(T,round(math:sqrt(T))) > 1000) of To: case (divisor(T,round(math:sqrt(T))) > 1000) of

This will produce the correct answer for the Erlang multi-process example. 這將爲Erlang多進程示例生成正確答案。


#6樓

I modified "Jannich Brendle" version to 1000 instead 500. And list the result of euler12.bin, euler12.erl, p12dist.erl. 我將“Jannich Brendle”版本修改爲1000而不是500.並列出了euler12.bin,euler12.erl,p12dist.erl的結果。 Both erl codes use '+native' to compile. 兩個erl代碼都使用'+ native'進行編譯。

zhengs-MacBook-Pro:workspace zhengzhibin$ time erl -noshell -s p12dist start
The result is: 842161320.

real    0m3.879s
user    0m14.553s
sys     0m0.314s
zhengs-MacBook-Pro:workspace zhengzhibin$ time erl -noshell -s euler12 solve
842161320

real    0m10.125s
user    0m10.078s
sys     0m0.046s
zhengs-MacBook-Pro:workspace zhengzhibin$ time ./euler12.bin 
842161320

real    0m5.370s
user    0m5.328s
sys     0m0.004s
zhengs-MacBook-Pro:workspace zhengzhibin$
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章