Professional Documents
Culture Documents
self.P = COMM_WORLD.Get_size()
self.rank = COMM_WORLD.Get_rank()
def wait(self):
if self.rank == 0:
raise RuntimeError("Proc 0 cannot
wait!")
status = MPI.Status()
while True:
task = COMM_WORLD.recv(source=0,
tag=MPI.ANY_TAG, status=status)
if not task:
break
if isinstance(task, FunctionType):
self.f = task
continue
result = self.f(task)
COMM_WORLD.isend(result, dest=0,
tag=status.tag)
def map(self, f, tasks):
N = len(tasks)
P = self.P
Pless1 = P - 1
if self.rank != 0:
self.wait()
return
if f is not self.f:
self.f = f
requests = []
for p in range(1, self.P):
r = COMM_WORLD.isend(f, dest=p)
requests.append(r)
MPI.Request.waitall(requests)
requests = []
for i, task in enumerate(tasks):
r = COMM_WORLD.isend(task, dest=(i
%Pless1)+1, tag=i)
requests.append(r)
MPI.Request.waitall(requests)
results = []
for i in range(N):
result = COMM_WORLD.recv(source=(i
%Pless1)+1, tag=i)
results.append(result)
return results
def __del__(self):
if self.rank == 0:
for p in range(1, self.P):
COMM_WORLD.isend(False, dest=p)
If the task was not a function, then it must be a real task. Call
the function on this task and send back the result.
A map() method to be used like before.
Make the workers wait while the master sends out tasks.
just another worker. The Pool class has to jointly fulfill both the
worker and the master roles.
The wait() method here has the same meaning
as Thread.run() from Threads. It does work when there is
work to do and sits idle otherwise. There are three paths
that wait() can take, depending on the kind of task it receives:
1. If a function was received, it assigns this function to the
attribute f for later use.
2. If an actual task was received, it calls the f attribute with the
task as an argument.
3. If the task is False, then it stops waiting.
The master process is not allowed to wait and therefore not allowed
to do real work. We can take this into account by telling MPI to
use P+1 nodes. This is similar to what we saw with threads.
However, with MPI we have to handle the master process explicitly.
With Python threading, Python handles the main thread, and thus the
master process, for us.
The map() method again takes a function and a list of tasks. The
tasks are evenly distributed over the workers. Themap() method is
only runnable on the master, while workers are told to wait. If the
function that is passed in is different than the current value of
the f attribute, then the function itself is sent to all of the workers.
Sending happens via the initiate send (COMM_WORLD.isend())
call. We ensure that the function has made it to all of the workers via
the call toMPI.Request.waitall(). This acts as an
acknowledgment between the sender and all of the receivers. Next,
the tasks are distributed to their appropriate ranks. Finally, the
results are received from the workers.
When the master pool instance is deleted, it will automatically
instruct the workers to stop waiting. This allows the workers to be
cleaned up correctly as well. Since the Pool API here is different
enough, a new version of the top-levelsimulate() function must
While there is a speedup for the P=2 case, it is only about 1.4x,
rather than the hoped-for 2x. The downward trend forP>2 is still
present, and even steeper than with multiprocessing. Furthermore,
the P=1 MPI case is about 5.5x slower than the same simulation
with no parallelism. So, for small simulations MPIs overhead may
not be worth it.
Still, the situation presented here is a worst-case scenario for MPI:
arbitrary Python code with two-way communication on a small