我从问题#12 Project
Euler作为编程练习,并比较我在C,Python,Erlang和Haskell中的实现(当然不是最优)实现。为了获得更高的执行时间,我搜索了第一个有1000个以上因子的三角形数字,而不是原始问题中所述的500个。
结果如下:
<强> C:
[email protected]:~/erlang$ gcc -lm -o euler12.bin euler12.c
[email protected]:~/erlang$ time ./euler12.bin
842161320
real 0m11.074s
user 0m11.070s
sys 0m0.000s
<强>的Python:
[email protected]:~/erlang$ time ./euler12.py
842161320
real 1m16.632s
user 1m16.370s
sys 0m0.250s
Python与PyPy:**
[email protected]:~/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
<强>二郎:
[email protected]:~/erlang$ erlc euler12.erl
[email protected]:~/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中:
[email protected]:~/erlang$ ghc euler12.hs -o euler12.hsx
[1 of 1] Compiling Main ( euler12.hs, euler12.o )
Linking euler12.hsx ...
[email protected]:~/erlang$ time ./euler12.hsx
842161320
real 2m37.326s
user 2m37.240s
sys 0m0.080s
<强>要点:
- C:100%
- Python:692%(使用PyPy的占118%)
- Erlang:436%(135%感谢RichardC)
- Haskell:1421%
我认为C有一个很大的优势,因为它用了很长的时间来计算,而不是任意长度的整数。另外它不需要首先加载运行时(其他人?)。
问题1: Erlang,Python和Haskell会因为使用任意长度的整数而失去速度,或者只要值小于MAXINT
?
问题2: 为什么Haskell这么慢?有没有编译器标志,关闭刹车或是我的实施? (后者很可能,因为Haskell是一本有七个印章的书。)
问题3: 你可以提供一些提示,如何优化这些实现而不改变我确定因素的方式?以任何方式优化:更好,更快,更“原生”的语言。
**修改
问题4: 我的功能实现是否允许LCO(最后一次调用优化,又名尾递归消除),从而避免在调用堆栈中添加不必要的帧?
尽管我不得不承认我的Haskell和Erlang知识是非常有限的,但我真的试图在四种语言中尽可能地实现相同的算法。
使用的源代码:
#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 ("%ldn", 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
在x86_64 Core2 Duo(2.5GHz)机器上使用GHC 7.0.3
,gcc 4.4.6
,Linux
编译使用
2.6.29ghc -O2 -fllvm -fforce-recomp
用于Haskell和gcc -O3 -lm
用于C。
- 您的C例程运行时间为8.4秒(比运行速度快,可能是因为
-O3
) - Haskell解决方案在36秒内运行(由于
-O2
标志) - 您的
factorCount'
代码没有显式键入,并且默认为Integer
(感谢Daniel纠正我的错误!使用Int
和时间更改为 11.1秒
提供显式类型签名(这是标准练习)在
factorCount’中,你有不必要的
fromIntegral
`。修正后的结果没有改变(编译器很聪明,对你来说很幸运)。 - 您使用了
mod
,其中rem
更快更充分。这会将时间更改为 8.5秒 。 factorCount'大专栏 与项目欧拉速度比较:C vs Python与Erlang vs Haskellode>不断地应用两个额外的参数(
number
,sqrt
)。工人/包装转换给我们:
$ time ./so
842161320
real 0m7.954s
user 0m7.944s
sys 0m0.004s
没错, 7.95秒 。始终比C解决方案快0.5秒。**如果没有-fllvm
标志,我仍然得到8.182秒
,所以NCG后端在这种情况下也做得很好。
结论:Haskell真棒。
结果代码
factorCount number = factorCount' number isquare 1 0 - (fromEnum $ square == fromIntegral isquare)
where square = sqrt $ fromIntegral number
isquare = floor square
factorCount' :: Int -> Int -> Int -> Int -> Int
factorCount' number sqrt candidate0 count0 = go candidate0 count0
where
go candidate count
| candidate > sqrt = count
| number `rem` candidate == 0 = go (candidate + 1) (count + 2)
| otherwise = go (candidate + 1) count
nextTriangle index triangle
| factorCount triangle > 1000 = triangle
| otherwise = nextTriangle (index + 1) (triangle + index + 1)
main = print $ nextTriangle 1 1
编辑:所以现在我们已经探索了,让我们解决问题
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?
在Haskell中,使用Integer
比Int
慢,但是慢多少取决于所执行的计算。幸运的是(对于64位机器)Int
就足够了。为了可移植性,你应该重写我的代码来使用Int64
或者Word64
(C不是唯一带有long
的语言) p>
Question 2: Why is haskell so slow? 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.)
>
Question 3: Can you offer me some hints how to optimize these
implementations without changing the way I determine the factors? Optimization
in any way: nicer, faster, more “native” to the language.
这就是我上面回答的问题。答案是
- 0)通过
-O2
使用优化 - 1)尽可能使用快速(特别是:取消箱式)类型
2)rem
不是mod
(一个经常被遗忘的优化)和
3)工人/包装转换(也许是最常见的优化)
Question 4: Do my functional implementations permit LCO and hence avoid
adding unnecessary frames onto the call stack?
是的,这不是问题。好的工作,很高兴你考虑这个。
Erlang的实现有一些问题。作为以下的基准,我的未修改的Erlang程序的执行时间是47.6秒,而C代码是12.7秒。
如果你想运行计算密集的Erlang代码,你应该做的第一件事就是使用本地代码。用erlc + native
的时间缩短到了41.3秒。然而,这种代码的本地编译速度要低很多(只有15%),问题在于你使用了
euler12编译-compile(export_all)
。这对于实验是有用的,但所有功能都可以从外部访问的事实导致本地编译器非常保守。
(正常的BEAM模拟器没有太大的影响。)用-export([solve / 0])替换这个声明。
提供了更好的加速:31.5秒(几乎是基线的35%)。
但是代码本身存在一个问题:对于factorCount循环中的每次迭代_,执行此测试:
factorCount (_, Sqrt, Candidate, Count) when Candidate == Sqrt -> Count + 1;
C代码不这样做。一般来说,在相同代码的不同实现之间进行公平的比较是非常棘手的,特别是如果算法是数值的,因为你需要确定它们实际上是在做同样的事情。在一个实现中由于某种类型转换而导致的轻微舍入错误可能会导致它执行比其他更多的迭代,即使两者最终都达到相同的结果。
为了消除这个可能的错误源(并且在每次迭代中摆脱额外的测试),我重写了factorCount函数,如下所示,精确地模拟C代码:
factorCount (N) ->
Sqrt = math:sqrt (N),
ISqrt = trunc(Sqrt),
if ISqrt == Sqrt -> factorCount (N, ISqrt, 1, -1);
true -> factorCount (N, ISqrt, 1, 0)
end.
factorCount (_N, ISqrt, Candidate, Count) when Candidate > ISqrt -> Count;
factorCount ( N, ISqrt, Candidate, Count) ->
case N rem Candidate of
0 -> factorCount (N, ISqrt, Candidate + 1, Count + 2);
_ -> factorCount (N, ISqrt, Candidate + 1, Count)
end.
这个重写,没有export_all
和本地编译,给了我下面的运行时间:
$ erlc +native euler12.erl
$ time erl -noshell -s euler12 solve
842161320
real 0m19.468s
user 0m19.450s
sys 0m0.010s
这与C代码相比并不算太坏:
$ time ./a.out
842161320
real 0m12.755s
user 0m12.730s
sys 0m0.020s
考虑到Erlang完全不适合编写数字代码,在这样的程序上只比C慢50%。
最后,关于你的问题:
问题1:由于使用了任意长度的整数或者是erlang,python和haskell,所以速度很慢 只要数值小于MAXINT,是不是他们?**
是的,有点。在Erlang中,没有办法说“使用32/64位算术进行换行”,所以除非编译器能够证明整数(通常不能),否则必须检查所有的计算才能看到如果他们可以适应一个单一的标签的单词,或者如果它必须把它们变成堆分配bignums。即使在运行时实际上没有使用过这样的元素,这些检查也必须执行。另一方面,这意味着你知道如果你突然给它比以前更大的输入,算法将永远不会失败,因为一个意外的整数回绕。
问题4:我的功能实现是否允许使用LCO,从而避免在调用堆栈中添加不必要的帧?**
是的,您的Erlang代码在上次调用优化方面是正确的。
未经作者同意,本文严禁转载,违者必究!
原文地址:https://www.cnblogs.com/lijianming180/p/12026719.html