这个素数相关谓词的瓶颈是什么?

时间:2011-11-29 12:54:13

标签: performance prolog primes

所以这就是:我正在尝试计算低于两百万的所有素数之和(对于this problem),但我的程序非常慢。我知道这个算法本身就非常糟糕而且是一个蛮力的算法,但它似乎比它应该慢得多。
在这里,我将搜索限制为20,000,以便结果不会等待太长时间 我不认为这个谓词很难理解,但我还是会解释它:我计算所有素数低于20,000的列表,然后求它们。总和部分很好,素数部分非常慢。

problem_010(R) :-
    p010(3, [], Primes),
    sumlist([2|Primes], R).
p010(20001, Primes, Primes) :- !.
p010(Current, Primes, Result) :-
    (
        prime(Current, Primes)
    ->  append([Primes, [Current]], NewPrimes)
    ;   NewPrimes = Primes
    ),
    NewCurrent is Current + 2,
    p010(NewCurrent, NewPrimes, Result).
prime(_, []) :- !.
prime(N, [Prime|_Primes]) :- 0 is N mod Prime, !, fail.
prime(ToTest, [_|Primes]) :- prime(ToTest, Primes).

我想了解为什么它如此缓慢。它是一个很好的实现愚蠢的暴力算法,还是有一些原因使Prolog下降?

编辑:我已经找到了一些东西,通过添加新的素数而不是让它们在列表的头部,我有更多经常在开始时出现的素数,所以它快〜3倍。仍然需要一些见解:))

4 个答案:

答案 0 :(得分:4)

首先,Prolog在这里不会失败。

如何生成素数有很聪明的方法。但是,作为一个廉价的开始只是按相反的顺序积累素数! (7.9s - > 2.6s)以这种方式,较小的那些被更快地测试。然后,考虑仅测试高达141的素数。较大的质数不能成为一个因素。

然后,您可以添加3,5,7,而不是仅通过不能被2整除的数字进行踩踏。

有人在写这个“问题”的论文。例如,参见this paper,尽管对于“真正的”算法实际上是多么复杂的讨论,22世纪前最新版本的算盘被称为Salamis tablets

答案 1 :(得分:3)

首先,使用append/3追加到列表的末尾非常慢。如果必须,请改用差异列表。 (就个人而言,我尽量避免append/3

其次,在检查素数时,您的prime/2总是遍历整个列表。这不必要地慢。您只需检查ID,就可以找到一个积分因子,直到您要检查的数字的平方根。

problem_010(R) :-
    p010(3, 2, R).
p010(2000001, Primes, Primes) :- !.
p010(Current, In, Result) :-
    ( prime(Current) -> Out is In+Current ; Out=In ),
    NewCurrent is Current + 2,
    p010(NewCurrent, Out, Result).

prime(2).
prime(3).
prime(X) :-
    integer(X),
    X > 3,
    X mod 2 =\= 0,
    \+is_composite(X, 3).   % was: has_factor(X, 3)

is_composite(X, F) :-       % was: has_factor(X, F) 
    X mod F =:= 0, !.
is_composite(X, F) :- 
    F * F < X,
    F2 is F + 2,
    is_composite(X, F2).

免责声明:我通过Google搜索找到了prime/1has_factor/2的此实现。

此代码给出:

?- problem_010(R).
R = 142913828922
Yes (12.87s cpu)

这是更快的代码:

problem_010(R) :-
    Max = 2000001,
    functor(Bools, [], Max),
    Sqrt is integer(floor(sqrt(Max))),
    remove_multiples(2, Sqrt, Max, Bools),
    compute_sum(2, Max, 0, R, Bools).

% up to square root of Max, remove multiples by setting bool to 0
remove_multiples(I, Sqrt, _, _) :- I > Sqrt, !.
remove_multiples(I, Sqrt, Max, Bools) :-
    arg(I, Bools, B),
    (
        B == 0
    ->
        true % already removed: do nothing
    ;
        J is 2*I, % start at next multiple of I
        remove(J, I, Max, Bools)
    ),
    I1 is I+1,
    remove_multiples(I1, Sqrt, Max, Bools).

remove(I, _, Max, _) :- I > Max, !.
remove(I, Add, Max, Bools) :-
     arg(I, Bools, 0), % remove multiple by setting bool to 0
     J is I+Add,
     remove(J, Add, Max, Bools).

% sum up places that are not zero
compute_sum(Max, Max, R, R, _) :- !.
compute_sum(I, Max, RI, R, Bools) :-
    arg(I, Bools, B),
    (B == 0 -> RO = RI ;  RO is RI + I ),
    I1 is I+1,
    compute_sum(I1, Max, RO, R, Bools).

这比我上面给出的代码快了一个数量级:

?- problem_010(R).
R = 142913828922
Yes (0.82s cpu)

答案 2 :(得分:3)

考虑使用例如筛子方法(“Eratosthenes的筛子”):首先使用例如numlist / 3创建列表[2,3,4,5,6,...... N]。列表中的第一个数字是素数,保留它。从列表的其余部分中消除其倍数。剩余列表中的下一个数字也是素数。再次消除它的倍数。等等。该列表将迅速缩小,最终只剩下素数。

答案 3 :(得分:2)

好的,在编辑之前问题只是算法(imho)。 正如您所注意到的,检查数字是否先用较小的素数进行分割会更有效;在有限集合中,有更多的数字被3整除,而不是32147。

另一种算法改进是停止检查素数何时大于数字的平方根。

现在,在你改变之后确实存在一些prolog问题: 你使用append / 3。 append / 3非常慢,因为你必须遍历整个列表才能将元素放在最后。 相反,你应该使用差异列表,这样可以非常快速地将元素放在尾部。

现在,差异清单是什么?而不是创建一个普通的列表[1,2,3],你创建这个[1,2,3 | T]。请注意,我们将尾部未实例化。然后,如果我们想在列表的末尾添加一个元素(或更多),我们可以简单地说T = [4 | NT]。真棒?

以下解决方案(以相反的顺序累积素数,在素数&gt; sqrt(N)时停止,要追加的差异列表)对于20k素数需要0.063,对于2m素数需要17秒,而原始代码需要3.7秒用于20k且附加/ 3版本1.3秒。

problem_010(R) :-
    p010(3, Primes, Primes),
    sumlist([2|Primes], R).
p010(2000001, _Primes,[]) :- !.                                %checking for primes till 2mil
p010(Current, Primes,PrimesTail) :-
    R is sqrt(Current),
    (
        prime(R,Current, Primes)
    ->  PrimesTail = [Current|NewPrimesTail]
    ;   NewPrimesTail = PrimesTail
    ),
    NewCurrent is Current + 2,
    p010(NewCurrent, Primes,NewPrimesTail).
prime(_,_, Tail) :- var(Tail),!.
prime(R,_N, [Prime|_Primes]):-
    Prime>R.
prime(_R,N, [Prime|_Primes]) :-0 is N mod Prime, !, fail.
prime(R,ToTest, [_|Primes]) :- prime(R,ToTest, Primes).

另外,考虑在生成它们时添加数字以避免额外的o(n)因为sumlist / 2
最后,您始终可以实现在多项式时间(XD)

中运行的AKS算法