Одновременное применение предиката для фильтрации списка (пролог SWI)

Моя проблема: применить предикат для фильтрации списка параллельно

У меня есть список, и у меня есть предикат. На практике это длинный список, и предикат требует времени. Я хочу вывести только те элементы списка, которые удовлетворяют предикату.

У меня есть доступ к большому количеству процессорных ядер, поэтому я подумал, что имеет смысл попытаться параллельно протестировать элементы списка блоками.

Материал, который я пробовал:

[concurrent_]maplist не работает

Вроде должно быть легко. concurrent_maplist кажется простой параллельной версией maplist.

Согласно другому ответу на этом сайте, maplist/3 должен делать именно то, что я хочу. Документация для 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 [править] бежит! Но параллельно?

Я пытался заставить его работать, используя concurrent/3 частично по примеру другого ответа.

Загрузка этого файла:

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 (с гораздо более длинным предикатом вместо этого f), кажется, что он работает только на одном ядре :-(.

Как сделать это параллельно?

Есть ли способ добиться этого с помощью concurrent_maplist или аналогичной простой параллельной версии include, или другого способа добиться того, что я собираюсь сделать?


person LangeHaare    schedule 28.02.2018    source источник


Ответы (2)


давайте протестируем 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 и сохранять результаты либо в базе данных, либо в глобальных переменных, и т. д.

person CapelliC    schedule 03.03.2018

maplist принимает первый аргумент в качестве предиката и применяет в качестве аргумента к этому предикату один элемент из каждого списка, заданного в качестве аргументов для maplist. Это обязательно означает, что все аргументы списка для maplist имеют одинаковое количество элементов. В этом случае f принимает один аргумент, но maplist(f, _, _) ожидает, что f примет 2 аргумента. Таким образом, ошибка Неопределенная процедура: f/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 = []
    ).

Или что-то в этом роде (не проверено).

После всего этого я, вероятно, не ответил на часть вопроса о «параллелизме».

person lurker    schedule 28.02.2018
comment
Спасибо, include (в SWI) выполняет работу, как вы описали с помощью select. Моя главная проблема - часть параллелизма, но идея "выбрать до отказа" полезна, спасибо! - person LangeHaare; 01.03.2018