本文翻译自: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, jannich@bredsaal.dk, 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$