与项目欧拉速度比较:C vs Python与Erlang vs Haskell

PHP学习笔记 2019-12-12

我从问题#12 作为编程练习,并比较我在C,Python,Erlang和Haskell中的实现(当然不是最优)实现。为了获得更高的执行时间,我搜索了第一个有1000个以上因子的三角形数字,而不是原始问题中所述的500个。

结果如下:

<强> C:

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

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

<强>的Python:

:~/erlang$ time ./euler12.py 
842161320

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

Python与PyPy:**

:~/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$ erlc euler12.erl 
:~/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中:

:~/erlang$ ghc euler12.hs -o euler12.hsx
[1 of 1] Compiling Main             ( euler12.hs, euler12.o )
Linking euler12.hsx ...
:~/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.3gcc 4.4.6Linux 2.6.29编译使用ghc -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'<a target="_blank" href="https://www.ancii.com/link/v1/fNpc51cBT8oNJ9Rh22mDPvgCpHjcy_mYL8IUMzo0LFc/" rel="nofollow" title="大专栏">大专栏</a>  <a target="_blank" href="https://www.ancii.com/link/v1/fNpc51cBT8oNJ9Rh22mDPnrYW_oyfkxG4eztqHknjHE7B8R8hKslYKuln4BwFedsAYHPky3Is0y01dDZ0SemFQ/" rel="nofollow" title="与项目欧拉速度比较:C vs Python与Erlang vs Haskell">与项目欧拉速度比较:C vs Python与Erlang vs Haskell</a>ode>不断地应用两个额外的参数(<code>numbersqrt)。工人/包装转换给我们:
$ 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中,使用IntegerInt慢,但是慢多少取决于所执行的计算。幸运的是(对于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 euler12编译的时间缩短到了41.3秒。然而,这种代码的本地编译速度要低很多(只有15%),问题在于你使用了-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代码在上次调用优化方面是正确的。

未经作者同意,本文严禁转载,违者必究!

相关推荐