同时应用谓词来过滤列表(SWI Prolog)

Concurrently applying a predicate to filter a list (SWI Prolog)

我的问题:应用谓词过滤列表并行

我有一个列表,我有一个谓词。实际上,这是一个很长的列表,谓词需要一段时间。我只想输出列表中满足谓词的元素。

我可以访问很多 CPU 个核心,所以我认为尝试并行测试块中的列表元素是有意义的。

我试过的东西:

[concurrent_]maplist 不起作用

看起来应该很容易。 concurrent_maplist 似乎是 maplist.

的简单并发版本

根据 another answer on this site, maplist/3 should do exactly what I want. The docs 对于 SWI 的 maplist/3 建议,它的行为可能与答案中描述的不一样,但在评论中,答案的作者表明这是文档和它确实应该按预期工作。

它似乎对我不起作用。

我测试如下:

:- use_module(library(apply)).

f(A):-
   (A*A < 10),
   writeln(A).

 set(0,[]).
 set(N,T):- N2 is N-1, set(N2,T1), append(T1,[N],T).

这不起作用:

?- set(4,L), concurrent_maplist(f, L, O).
ERROR: Undefined procedure: f/2
ERROR:   However, there are definitions for:
ERROR:         f/1

与普通 maplist 相同的问题:

set(4,L), maplist(f, L, O).
ERROR: Undefined procedure: f/2
ERROR:   However, there are definitions for:
ERROR:         f/1

include 有效但不平行

做的做我想做的(不是并行的)是include:

?- set(11,L), include(f, L, O).
1
2
3
L = [1, 2, 3, 4, 5, 6, 7, 8, 9|...],
O = [1, 2, 3] .

concurrent [编辑] 运行s!但它是平行的吗?

我一直在尝试使用 concurrent/3 partly following the example of another answer 来让它工作。

正在加载此文件:

set(0,[]):-!.
set(N,T):- N2 is N-1, set(N2,T1), append(T1,[N],T).
f(A, B):-
    (A*A < B),
    writeln(A).
par_test(A, B):-
    set(A, S),
    findall(f(Good, B), (member(Good, S)), Goals),
    concurrent(8, Goals, []).

...

?- set(4, S), findall(f(Good, 4), member(Good, S), Goals), concurrent(8, Goals, []).
1
false.

但是观察 htop(用一个更长的 运行ning 谓词代替这个 f),它似乎 运行 只在一个核心上 :- (.

如何并行执行此操作?

有没有办法用 concurrent_maplist 或类似简单的并行版本 include 或其他方法来实现我的目标?

maplist 将第一个参数作为谓词,并将作为参数的每个列表中的一个元素应用于此谓词,作为 maplist 的参数。这必然意味着 maplist 的所有列表参数都具有相同数量的元素。在这种情况下 f 接受一个参数,但 maplist(f, _, _) 期望 f 接受 2 个参数。因此,错误 Undefined procedure: f/2 这意味着 Prolog 找不到带有 2 个参数的 f

所以 maplist 并没有真正按照您的意愿行事。您想要在某些其他语言中称为 select 的东西,在这些语言中,您只在第二个列表中包含元素,这些元素通过了应用于第一个元素的过滤器。

这是一个示例实现:

select(_, [], []).
select(Filter, [R|Rs], Fs) :-
    (   call(Filter, R)
    ->  Fs = [R|SF]
    ;   Fs = SF
    ),
    select(Filter, Rs, SF).

并调用,例如,select(f, L, O)

如果像您的示例一样,您有任何带有 属性 的过滤器,它们将成功达到列表中的某个点,然后继续失败,您可能想要像这样优化过滤器,不要让它在第一次失败后继续遍历列表。

select_until_fail(_, [], []).
select_until_fail(Filter, [R|Rs], Fs) :-
    (   call(Filter, R)
    ->  Fs = [R|SF],
        select_until_fail(Filter, Rs, SF)
    ;   Fs = []
    ).

或类似的东西(未经测试)。

毕竟,我可能没有真正回答问题的 "concurrency" 部分。

让我们测试一下 concurrent_maplist:

test_concurrent_maplist(C) :-
    numlist(1,C,Ns),
    concurrent_maplist(prime,Ns,Ps),
    sumlist(Ps,N),
    format('we have ~d primes in first ~d natural numbers~n', [N,C]).
test_maplist(C) :-
    numlist(1,C,Ns),
    maplist(prime,Ns,Ps),
    sumlist(Ps,N),
    format('we have ~d primes in first ~d natural numbers~n', [N,C]).

prime(N,1) :-
    prime(N), !.
prime(_,0).

% from https://en.wikipedia.org/wiki/Primality_test
prime(N) :- N =< 1, !, fail.
prime(N) :- N =< 3, !.
prime(N) :- (0 is N mod 2; 0 is N mod 3), !, fail.
prime(N) :- prime_(N,5).

prime_(N,I) :-
    I * I =< N, !,
    ( ( 0 is N mod I; 0 is N mod (I + 2) ) -> fail
    ; J is I + 1, prime_(N,J)
    ).
prime_(_,_).

在 4 核机器上,它产生

?- time(test_concurrent_maplist(100000)).
we have 9592 primes in first 100000 natural numbers
% 2,100,109 inferences, 1.799 CPU in 2.217 seconds (81% CPU, 1167205 Lips)
true.

?- time(test_concurrent_maplist(1000000)).
we have 78498 primes in first 1000000 natural numbers
% 21,000,109 inferences, 18.244 CPU in 36.764 seconds (50% CPU, 1151063 Lips)
true.

?- time(test_maplist(100000)).
we have 9592 primes in first 100000 natural numbers
% 16,151,437 inferences, 3.942 CPU in 3.951 seconds (100% CPU, 4096903 Lips)
true.

?- time(test_maplist(1000000)).
we have 78498 primes in first 1000000 natural numbers
% 402,953,287 inferences, 102.334 CPU in 102.302 seconds (100% CPU, 3937617 Lips)
true.

毫无疑问,一个改进:

?- F1 is (2.217/3.951)*100, F2 is (36.764/102.334)*100.

对于更长的列表,我们接近运行时间的 1/3。

而不是 concurrent_maplist/3,我们可以坚持 concurrent_maplist/2 并将结果存储在数据库、全局变量等中...