python -i initonerun.py
onerun(x)
onerun(0.3)
initonerun.py is organized in a modular fashion. Only highlights are mentioned.
python -i initbatser.py
initbatser.py is based on initonerun.py. Only significant differences are mentioned.
trun = time.time()
records system time at the beginning of the code
whose run time will be evaluated.for
loop that iterates the run counter run_id
from 0 to NRUNS - 1. Each pass through this loop results in a new simulation with a
new stimulus amplitude, finds the spike frequency, and saves the stimulus amplitude
and frequency to a pair of lists. It also prints a message to the terminal to indicate
progress.
python initbatpar.py
mpiexec -n N python initbatpar.py
initbatpar.py is based on initbatser.py. Only key differences are mentioned below.
The serial program initbatser.py has a batchrun(n) that uses
this for
loop
to execute a series of simulations, one at a time, on a single processor:
for run_id in range(n): set_params(run_id) h.run() stims.append(stim.amp) freqs.append(get_frequency()) print('Finished %d of %d.' % (run_id + 1, n))In initbatpar.py, everything that can be offloaded to the workers has been taken out of batchrun() and inserted into a new function
fi(run_id)
that is defined prior to batchrun.
def fi(run_id): """set params, execute a simulation, analyze and return results""" set_params(run_id) h.run() return (run_id, stim.amp, get_frequency(spvec))Notice that
fi
contains the procedures that involve the most computational
overhead. Also notice that fi
expects a single numerical argument -- the run_id --
and returns a tuple with the run_id, the value of the stimulus, and the frequency obtained from the simulation.
An alternative implementation could have reduced communication by returning only the frequency, unpacking the
job index (equal to the run_id), and recomputing the stimulus amplitude. It is important to balance convenience, the aim
of keeping the workers busy, and minimizing communication overhead.
Here is initbatpar.py's batchrun procedure:
def batchrun(n): # preallocate lists of length n for storing parameters and results stims = [None] * n freqs = [None] * n for run_id in range(n): pc.submit(fi, run_id) count = 0 while pc.working(): run_id, amp, freq = pc.pyret() stims[run_id] = amp freqs[run_id] = freq count += 1 print('Finished %d of %d.' % (count, n)) return stims, freqsThere still is a
for
loop,
but it uses pc.submit() to post jobs to the bulletin board.
Communication is minimized by passing only the function handle (fi
)
and the simulation index run_id for each run that is to be carried out.
Next comes a while
loop
in which the master checks the bulletin board
for returned results.
If nothing is found, the master picks a task from the bulletin board
and executes it.
If a result is present, the master retrieves it from the bulletin board:
pc.pyret() gets the value returned by fi
,
which is unpacked into its three components.
run_id is used to place the results in the appropriate locations in the stims and freqs lists, as there is no guarantee that simulation results will be returned in any specific sequence.
After the last job has been completed,
the master exits the while
loop,
and batchrun is finished.
Then pc.done() releases the workers.
But the master still has to save the results.