В главе «Программирование многоядерных процессоров» книги «Программирование на Erlang» Джо Армстронг приводит хороший пример распараллеливания функции карты:
pmap(F, L) ->
S = self(),
%% make_ref() returns a unique reference
%% we'll match on this later
Ref = erlang:make_ref(),
Pids = map(fun(I) ->
spawn(fun() -> do_f(S, Ref, F, I) end)
end, L),
%% gather the results
gather(Pids, Ref).
do_f(Parent, Ref, F, I) ->
Parent ! {self(), Ref, (catch F(I))}.
gather([Pid|T], Ref) ->
receive
{Pid, Ref, Ret} -> [Ret|gather(T, Ref)]
end;
gather([], _) ->
[].
Это работает хорошо, но я считаю, что в нем есть узкое место, заставляющее его работать очень медленно в списках с более чем 100 000 элементов.
Когда выполняется функция gather()
, она начинает сопоставлять первый Pid
из списка Pids
с сообщением в основном почтовом ящике процесса. Но что, если самое старое сообщение в почтовом ящике не из этого самого Pid
? Затем он пробует все остальные сообщения, пока не найдет совпадение. При этом существует определенная вероятность того, что при выполнении функции gather()
нам пришлось бы перебирать все сообщения почтового ящика, чтобы найти совпадение с Pid
, которое мы взяли из списка Pids
. Это N * N худший вариант для списка размером N.
Мне даже удалось доказать существование этого узкого места:
gather([Pid|T], Ref) ->
receive
{Pid, Ref, Ret} -> [Ret|gather(T, Ref)];
%% Here it is:
Other -> io:format("The oldest message in the mailbox (~w) did not match with Pid ~w~n", [Other,Pid])
end;
Как мне избежать этого узкого места?