2010-08-10 09:25:24 +08:00
|
|
|
\section{Parallelization layer}
|
2010-08-17 02:00:47 +08:00
|
|
|
Mitsuba is built on top of a flexible parallelization layer, which spreads out
|
2010-08-16 23:48:29 +08:00
|
|
|
various types of computation over local and remote cores.
|
|
|
|
The guiding principle is that if an operation can potentially take longer than a
|
|
|
|
few seconds, it ought to use all the cores it can get.
|
|
|
|
|
2010-08-17 02:00:47 +08:00
|
|
|
Here, we will go through a basic example, which will hopefully provide sufficient intuition
|
2012-10-25 02:44:26 +08:00
|
|
|
to realize more complex tasks.
|
2010-08-16 23:48:29 +08:00
|
|
|
To obtain good (i.e. close to linear) speedups, the parallelization layer depends on
|
|
|
|
several key assumptions of the task to be parallelized:
|
|
|
|
\begin{itemize}
|
2010-08-17 02:00:47 +08:00
|
|
|
\item The task can easily be split up into a discrete number of \emph{work units}, which requires a negligible amount of computation.
|
2012-10-25 02:44:26 +08:00
|
|
|
\item Each work unit is small in footprint so that it can easily be transferred over the network or shared memory.
|
2010-08-16 23:48:29 +08:00
|
|
|
\item A work unit constitutes a significant amount of computation, which by far outweighs the cost of transmitting it to another node.
|
2010-08-17 02:00:47 +08:00
|
|
|
\item The \emph{work result} obtained by processing a work unit is again small in footprint, so that it can easily be transferred back.
|
|
|
|
\item Merging all work results to a solution of the whole problem requires a negligible amount of additional computation.
|
2010-08-16 23:48:29 +08:00
|
|
|
\end{itemize}
|
2012-10-25 02:44:26 +08:00
|
|
|
This essentially corresponds to a parallel version of \emph{Map} (one part of \emph{Map\&Reduce}) and is
|
|
|
|
ideally suited for most rendering workloads.
|
2010-08-16 23:48:29 +08:00
|
|
|
|
2012-10-25 02:44:26 +08:00
|
|
|
The example we consider here computes a \code{ROT13} ``encryption'' of a string, which
|
2010-08-17 02:00:47 +08:00
|
|
|
most certainly violates the ``significant amount of computation'' assumption.
|
2010-08-17 07:27:08 +08:00
|
|
|
It was chosen due to the inherent parallelism and simplicity of this task.
|
2012-10-25 02:44:26 +08:00
|
|
|
While of course over-engineered to the extreme, the example hopefully
|
2010-08-17 02:00:47 +08:00
|
|
|
communicates how this framework might be used in more complex scenarios.
|
|
|
|
|
|
|
|
We will implement this program as a plugin for the utility launcher \code{mtsutil}, which
|
|
|
|
frees us from having to write lots of code to set up the framework, prepare the
|
|
|
|
scheduler, etc.
|
|
|
|
|
|
|
|
We start by creating the utility skeleton file \code{src/utils/rot13.cpp}:
|
|
|
|
\begin{cpp}
|
|
|
|
#include <mitsuba/render/util.h>
|
|
|
|
|
|
|
|
MTS_NAMESPACE_BEGIN
|
|
|
|
|
|
|
|
class ROT13Encoder : public Utility {
|
|
|
|
public:
|
|
|
|
int run(int argc, char **argv) {
|
|
|
|
cout << "Hello world!" << endl;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-09-02 23:57:04 +08:00
|
|
|
MTS_DECLARE_UTILITY()
|
2010-08-17 02:00:47 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
MTS_EXPORT_UTILITY(ROT13Encoder, "Perform a ROT13 encryption of a string")
|
|
|
|
MTS_NAMESPACE_END
|
|
|
|
\end{cpp}
|
|
|
|
The file must also be added to the build system: insert the line
|
|
|
|
\begin{shell}
|
2013-07-22 06:20:23 +08:00
|
|
|
plugins += $\texttt{env}$.SharedLibrary('rot13', ['rot13.cpp'])
|
2010-08-17 02:00:47 +08:00
|
|
|
\end{shell}
|
2013-07-22 06:20:23 +08:00
|
|
|
into the \code{utils/SConscript} file. After compiling
|
2010-08-17 02:00:47 +08:00
|
|
|
using \code{scons}, the \code{mtsutil} binary should automatically pick up your new utility plugin:
|
|
|
|
\begin{shell}
|
|
|
|
$\texttt{\$}$ mtsutil
|
|
|
|
..
|
|
|
|
The following utilities are available:
|
|
|
|
|
|
|
|
addimages Generate linear combinations of EXR images
|
|
|
|
rot13 Perform a ROT13 encryption of a string
|
|
|
|
\end{shell}
|
|
|
|
It can be executed as follows:
|
|
|
|
\begin{shell}
|
|
|
|
$\texttt{\$}$ mtsutil rot13
|
|
|
|
2010-08-16 18:38:27 INFO main [src/mitsuba/mtsutil.cpp:276] Mitsuba version 0.1.1, Copyright (c) 2010 Wenzel Jakob
|
|
|
|
2010-08-16 18:38:27 INFO main [src/mitsuba/mtsutil.cpp:350] Loading utility "rot13" ..
|
|
|
|
Hello world!
|
|
|
|
\end{shell}
|
|
|
|
|
2012-10-25 02:44:26 +08:00
|
|
|
Our approach for implementing distributed ROT13 will be to treat each character as an
|
2010-08-17 02:00:47 +08:00
|
|
|
indpendent work unit. Since the ordering is lost when sending out work units, we must
|
|
|
|
also include the position of the character in both the work units and the work results.
|
2010-08-16 23:48:29 +08:00
|
|
|
|
|
|
|
All of the relevant interfaces are contained in \code{include/mitsuba/core/sched.h}.
|
2010-08-17 02:00:47 +08:00
|
|
|
For reference, here are the interfaces of \code{WorkUnit} and \code{WorkResult}:
|
|
|
|
\begin{cpp}
|
|
|
|
/**
|
|
|
|
* Abstract work unit. Represents a small amount of information
|
2012-10-25 02:44:26 +08:00
|
|
|
* that encodes part of a larger processing task.
|
2010-08-17 02:00:47 +08:00
|
|
|
*/
|
|
|
|
class MTS_EXPORT_CORE WorkUnit : public Object {
|
|
|
|
public:
|
|
|
|
/// Copy the content of another work unit of the same type
|
|
|
|
virtual void set(const WorkUnit *workUnit) = 0;
|
|
|
|
|
|
|
|
/// Fill the work unit with content acquired from a binary data stream
|
|
|
|
virtual void load(Stream *stream) = 0;
|
|
|
|
|
|
|
|
/// Serialize a work unit to a binary data stream
|
|
|
|
virtual void save(Stream *stream) const = 0;
|
|
|
|
|
|
|
|
/// Return a string representation
|
|
|
|
virtual std::string toString() const = 0;
|
|
|
|
|
|
|
|
MTS_DECLARE_CLASS()
|
|
|
|
protected:
|
|
|
|
/// Virtual destructor
|
|
|
|
virtual ~WorkUnit() { }
|
|
|
|
};
|
|
|
|
/**
|
2012-10-25 02:44:26 +08:00
|
|
|
* Abstract work result. Represents the information that encodes
|
2010-08-17 02:00:47 +08:00
|
|
|
* the result of a processed <tt>WorkUnit</tt> instance.
|
|
|
|
*/
|
|
|
|
class MTS_EXPORT_CORE WorkResult : public Object {
|
|
|
|
public:
|
|
|
|
/// Fill the work result with content acquired from a binary data stream
|
|
|
|
virtual void load(Stream *stream) = 0;
|
|
|
|
|
|
|
|
/// Serialize a work result to a binary data stream
|
|
|
|
virtual void save(Stream *stream) const = 0;
|
|
|
|
|
|
|
|
/// Return a string representation
|
|
|
|
virtual std::string toString() const = 0;
|
|
|
|
|
|
|
|
MTS_DECLARE_CLASS()
|
|
|
|
protected:
|
|
|
|
/// Virtual destructor
|
|
|
|
virtual ~WorkResult() { }
|
|
|
|
};
|
|
|
|
\end{cpp}
|
|
|
|
In our case, the \code{WorkUnit} implementation then looks like this:
|
|
|
|
\begin{cpp}
|
|
|
|
class ROT13WorkUnit : public WorkUnit {
|
|
|
|
public:
|
|
|
|
void set(const WorkUnit *workUnit) {
|
2012-10-25 02:44:26 +08:00
|
|
|
const ROT13WorkUnit *wu =
|
2010-08-17 02:00:47 +08:00
|
|
|
static_cast<const ROT13WorkUnit *>(workUnit);
|
|
|
|
m_char = wu->m_char;
|
|
|
|
m_pos = wu->m_pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
void load(Stream *stream) {
|
|
|
|
m_char = stream->readChar();
|
|
|
|
m_pos = stream->readInt();
|
|
|
|
}
|
2012-10-25 02:44:26 +08:00
|
|
|
|
2010-08-17 02:00:47 +08:00
|
|
|
void save(Stream *stream) const {
|
|
|
|
stream->writeChar(m_char);
|
2012-10-25 02:44:26 +08:00
|
|
|
stream->writeInt(m_pos);
|
2010-08-17 02:00:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string toString() const {
|
|
|
|
std::ostringstream oss;
|
|
|
|
oss << "ROT13WorkUnit[" << endl
|
|
|
|
<< " char = '" << m_char << "'," << endl
|
|
|
|
<< " pos = " << m_pos << endl
|
|
|
|
<< "]";
|
|
|
|
return oss.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
inline char getChar() const { return m_char; }
|
|
|
|
inline void setChar(char value) { m_char = value; }
|
|
|
|
inline int getPos() const { return m_pos; }
|
|
|
|
inline void setPos(int value) { m_pos = value; }
|
|
|
|
|
|
|
|
MTS_DECLARE_CLASS()
|
|
|
|
private:
|
|
|
|
char m_char;
|
|
|
|
int m_pos;
|
|
|
|
};
|
|
|
|
|
|
|
|
MTS_IMPLEMENT_CLASS(ROT13WorkUnit, false, WorkUnit)
|
|
|
|
\end{cpp}
|
2012-10-25 02:44:26 +08:00
|
|
|
The \code{ROT13WorkResult} implementation is not reproduced since it is almost identical
|
2010-08-17 02:00:47 +08:00
|
|
|
(except that it doesn't need the \code{set} method).
|
2012-10-25 02:44:26 +08:00
|
|
|
The similarity is not true in general: for most algorithms, the work unit and result
|
2010-08-17 02:00:47 +08:00
|
|
|
will look completely different.
|
|
|
|
|
|
|
|
Next, we need a class, which does the actual work of turning a work unit into a work result
|
|
|
|
(a subclass of \code{WorkProcessor}). Again, we need to implement a range of support
|
2012-10-25 02:44:26 +08:00
|
|
|
methods to enable the various ways in which work processor instances will be submitted to
|
2010-08-17 02:00:47 +08:00
|
|
|
remote worker nodes and replicated amongst local threads.
|
|
|
|
\begin{cpp}
|
|
|
|
class ROT13WorkProcessor : public WorkProcessor {
|
|
|
|
public:
|
|
|
|
/// Construct a new work processor
|
|
|
|
ROT13WorkProcessor() : WorkProcessor() { }
|
|
|
|
|
2010-08-17 07:27:08 +08:00
|
|
|
/// Unserialize from a binary data stream (nothing to do in our case)
|
2010-08-17 02:00:47 +08:00
|
|
|
ROT13WorkProcessor(Stream *stream, InstanceManager *manager)
|
|
|
|
: WorkProcessor(stream, manager) { }
|
|
|
|
|
2010-08-17 07:27:08 +08:00
|
|
|
/// Serialize to a binary data stream (nothing to do in our case)
|
2010-08-17 02:00:47 +08:00
|
|
|
void serialize(Stream *stream, InstanceManager *manager) const {
|
|
|
|
}
|
|
|
|
|
|
|
|
ref<WorkUnit> createWorkUnit() const {
|
|
|
|
return new ROT13WorkUnit();
|
|
|
|
}
|
|
|
|
|
2012-10-25 02:44:26 +08:00
|
|
|
ref<WorkResult> createWorkResult() const {
|
2010-08-17 02:00:47 +08:00
|
|
|
return new ROT13WorkResult();
|
|
|
|
}
|
|
|
|
|
|
|
|
ref<WorkProcessor> clone() const {
|
2010-08-17 07:27:08 +08:00
|
|
|
return new ROT13WorkProcessor(); // No state to clone in our case
|
2010-08-17 02:00:47 +08:00
|
|
|
}
|
2010-08-16 23:48:29 +08:00
|
|
|
|
2010-08-17 02:00:47 +08:00
|
|
|
/// No internal state, thus no preparation is necessary
|
|
|
|
void prepare() { }
|
2010-08-16 23:48:29 +08:00
|
|
|
|
2010-08-17 02:00:47 +08:00
|
|
|
/// Do the actual computation
|
2012-10-25 02:44:26 +08:00
|
|
|
void process(const WorkUnit *workUnit, WorkResult *workResult,
|
2010-08-17 02:00:47 +08:00
|
|
|
const bool &stop) {
|
2012-10-25 02:44:26 +08:00
|
|
|
const ROT13WorkUnit *wu
|
2010-08-17 02:00:47 +08:00
|
|
|
= static_cast<const ROT13WorkUnit *>(workUnit);
|
|
|
|
ROT13WorkResult *wr = static_cast<ROT13WorkResult *>(workResult);
|
|
|
|
wr->setPos(wu->getPos());
|
|
|
|
wr->setChar((std::toupper(wu->getChar()) - 'A' + 13) % 26 + 'A');
|
|
|
|
}
|
|
|
|
MTS_DECLARE_CLASS()
|
|
|
|
};
|
2010-08-17 07:49:56 +08:00
|
|
|
MTS_IMPLEMENT_CLASS_S(ROT13WorkProcessor, false, WorkProcessor)
|
2010-08-17 02:00:47 +08:00
|
|
|
\end{cpp}
|
|
|
|
Since our work processor has no state, most of the implementations
|
2010-08-17 07:27:08 +08:00
|
|
|
are rather trivial. Note the \code{stop} field in the \code{process}
|
|
|
|
method. This field is used to abort running jobs at the users requests, hence
|
|
|
|
it is a good idea to periodically check its value during lengthy computations.
|
|
|
|
|
2010-10-31 23:06:26 +08:00
|
|
|
Finally, we need a so-called \emph{parallel process}
|
2010-08-17 07:27:08 +08:00
|
|
|
instance, which is responsible for creating work units and stitching
|
|
|
|
work results back into a solution of the whole problem. The \code{ROT13}
|
|
|
|
implementation might look as follows:
|
|
|
|
\begin{cpp}
|
2010-08-17 07:49:56 +08:00
|
|
|
class ROT13Process : public ParallelProcess {
|
|
|
|
public:
|
|
|
|
ROT13Process(const std::string &input) : m_input(input), m_pos(0) {
|
|
|
|
m_output.resize(m_input.length());
|
|
|
|
}
|
|
|
|
|
|
|
|
ref<WorkProcessor> createWorkProcessor() const {
|
|
|
|
return new ROT13WorkProcessor();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::string> getRequiredPlugins() {
|
|
|
|
std::vector<std::string> result;
|
|
|
|
result.push_back("rot13");
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
EStatus generateWork(WorkUnit *unit, int worker /* unused */) {
|
|
|
|
if (m_pos >= (int) m_input.length())
|
|
|
|
return EFailure;
|
|
|
|
ROT13WorkUnit *wu = static_cast<ROT13WorkUnit *>(unit);
|
|
|
|
|
|
|
|
wu->setPos(m_pos);
|
|
|
|
wu->setChar(m_input[m_pos++]);
|
|
|
|
|
|
|
|
return ESuccess;
|
|
|
|
}
|
|
|
|
|
|
|
|
void processResult(const WorkResult *result, bool cancelled) {
|
2012-10-25 02:44:26 +08:00
|
|
|
if (cancelled) // indicates a work unit, which was
|
2010-08-17 07:49:56 +08:00
|
|
|
return; // cancelled partly through its execution
|
2012-10-25 02:44:26 +08:00
|
|
|
const ROT13WorkResult *wr =
|
2010-08-17 07:49:56 +08:00
|
|
|
static_cast<const ROT13WorkResult *>(result);
|
|
|
|
m_output[wr->getPos()] = wr->getChar();
|
|
|
|
}
|
|
|
|
|
|
|
|
inline const std::string &getOutput() {
|
|
|
|
return m_output;
|
|
|
|
}
|
|
|
|
|
|
|
|
MTS_DECLARE_CLASS()
|
|
|
|
public:
|
|
|
|
std::string m_input;
|
|
|
|
std::string m_output;
|
|
|
|
int m_pos;
|
|
|
|
};
|
|
|
|
MTS_IMPLEMENT_CLASS(ROT13Process, false, ParallelProcess)
|
2010-08-17 07:27:08 +08:00
|
|
|
\end{cpp}
|
2010-08-17 07:49:56 +08:00
|
|
|
The \code{generateWork} method produces work units until we have moved past
|
|
|
|
the end of the string, after which it returns the status code \code{EFailure}.
|
2012-10-25 02:44:26 +08:00
|
|
|
Note the method \code{getRequiredPlugins()}: this is necessary to use
|
2010-08-17 07:49:56 +08:00
|
|
|
the utility across
|
|
|
|
machines. When communicating with another node, it ensures that the remote side
|
|
|
|
loads the \code{ROT13*} classes at the right moment.
|
|
|
|
|
|
|
|
To actually use the \code{ROT13} encoder, we must first launch the newly created parallel process
|
|
|
|
from the main utility function (the `Hello World' code we wrote earlier). We can adapt it as follows:
|
|
|
|
\begin{cpp}
|
|
|
|
int run(int argc, char **argv) {
|
|
|
|
if (argc < 2) {
|
|
|
|
cout << "Syntax: mtsutil rot13 <text>" << endl;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ref<ROT13Process> proc = new ROT13Process(argv[1]);
|
|
|
|
ref<Scheduler> sched = Scheduler::getInstance();
|
|
|
|
|
|
|
|
/* Submit the encryption job to the scheduler */
|
|
|
|
sched->schedule(proc);
|
|
|
|
|
|
|
|
/* Wait for its completion */
|
|
|
|
sched->wait(proc);
|
|
|
|
|
|
|
|
cout << "Result: " << proc->getOutput() << endl;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
\end{cpp}
|
2011-09-13 03:18:49 +08:00
|
|
|
After compiling everything using \code{scons}, a simple example
|
2012-10-25 02:44:26 +08:00
|
|
|
involving the utility would be to encode a string (e.g. \code{SECUREBYDESIGN}), while
|
2010-08-17 07:49:56 +08:00
|
|
|
forwarding all computation to a network machine. (\code{-p0} disables
|
2012-10-25 02:44:26 +08:00
|
|
|
all local worker threads). Adding a verbose flag (\code{-v}) shows
|
2010-08-17 07:49:56 +08:00
|
|
|
some additional scheduling information:
|
|
|
|
\begin{shell}
|
|
|
|
$\texttt{\$}$ mtsutil -vc feynman -p0 rot13 SECUREBYDESIGN
|
|
|
|
2010-08-17 01:35:46 INFO main [src/mitsuba/mtsutil.cpp:201] Mitsuba version 0.1.1, Copyright (c) 2010 Wenzel Jakob
|
|
|
|
2010-08-17 01:35:46 INFO main [SocketStream] Connecting to "feynman:7554"
|
|
|
|
2010-08-17 01:35:46 DEBUG main [Thread] Spawning thread "net0_r"
|
|
|
|
2010-08-17 01:35:46 DEBUG main [RemoteWorker] Connection to "feynman" established (2 cores).
|
|
|
|
2010-08-17 01:35:46 DEBUG main [Scheduler] Starting ..
|
|
|
|
2010-08-17 01:35:46 DEBUG main [Thread] Spawning thread "net0"
|
|
|
|
2010-08-17 01:35:46 INFO main [src/mitsuba/mtsutil.cpp:275] Loading utility "rot13" ..
|
|
|
|
2010-08-17 01:35:46 DEBUG main [Scheduler] Scheduling process 0: ROT13Process[unknown]..
|
|
|
|
2010-08-17 01:35:46 DEBUG main [Scheduler] Waiting $\texttt{for}$ process 0
|
|
|
|
2010-08-17 01:35:46 DEBUG net0 [Scheduler] Process 0 has finished generating work
|
|
|
|
2010-08-17 01:35:46 DEBUG net0_r[Scheduler] Process 0 is $\texttt{complete}$.
|
|
|
|
Result: FRPHEROLQRFVTA
|
|
|
|
2010-08-17 01:35:46 DEBUG main [Scheduler] Pausing ..
|
|
|
|
2010-08-17 01:35:46 DEBUG net0 [Thread] Thread "net0" has finished
|
|
|
|
2010-08-17 01:35:46 DEBUG main [Scheduler] Stopping ..
|
|
|
|
2010-08-17 01:35:46 DEBUG main [RemoteWorker] Shutting down
|
|
|
|
2010-08-17 01:35:46 DEBUG net0_r[Thread] Thread "net0_r" has finished
|
|
|
|
\end{shell}
|