High level interface example - TUM-I5/ASYNC GitHub Wiki
For the high level interface, you need to implement three classes:
- The dispatcher, responsible for splitting the communicator into compute and I/O nodes
- An I/O module responsible for one output
- An I/O executor
You have to implement only the last two. The dispatcher is provided by the library.
The I/O module:
#include "async/Module.h"
#include "MyIOExecutor.h"
class MyIOModule : async::Module<MyIOExecutor, InitParam, Param>
{
private:
MyIOExecutor executor;
unsigned long numElements;
public:
// This function will be executed for nodes
void setUp()
{
// Has to be called here
setExecutor(executor);
}
void init(const char* filename, const double* data, unsigned long numElements)
{
async::Module<MyIOExecutor, InitParam, Param>::init()
// Add buffer to transfer the file name
// The last parameter tells ASYNC that the buffer content will be the same on all nodes
addSyncBuffer(filename, strlen(filename), true);
// Add buffer to transfer the data (required later)
addBuffer(data, numElements*sizeof(double));
// Send filename
sendBuffer(0, strlen(filename));
// Call executor.execInit(param)
InitParam param;
callInit(param);
// Remove buffers no longer used
removeBuffer(0);
// Store the number of elements for later
this->numElements;
}
void write(double time)
{
// Wait for the previous I/O step
wait();
// Send the data buffer
sendBuffer(1, numElements * sizeof(double));
// Trigger the write, this will call executor.exec(param)
Param param;
param.time = time;
call(param);
}
void close()
{
// Wait for the last I/O step
wait();
// Finalize the I/O module
finalize();
}
// Is called on compute and I/O nodes
void tearDown()
{
// Has to be called here
executor.finalize();
}
};
The I/O executor:
#include "async/ExecInfo.h"
struct InitParam {
// List all parameters required for initialization
};
struct Param {
// List all parameters required for writing one time step:
double time;
};
class MyIOExecutor
{
private:
// file handle, etc.
public:
void execInit(const async::ExecInfo &info, const InitParam ¶m)
{
// Get the file name
const char* filename = static_cast<const char*>(info.buffer(0));
// Get the size of the data
unsigned long size = info.bufferSize(1) / sizeof(double);
// Open the file ...
}
void exec(const async::ExecInfo &info, const Param ¶m)
{
const double* data = static_cast<const double*>(info.buffer(1));
unsigned long size = info.bufferSize(1) / sizeof(double);
// Write the data
}
void finalize()
{
// Close the file
}
};
Putting everything together:
#include <mpi.h>
#include "async/Dispatcher.h"
#include "MyIOModule.h"
int main(int argc, char* argv[])
{
// MPI_THREAD_MULTIPLE is required if you use MPI (e.g. MPI-IO) in the executor
int provided;
MPI_Init_thread(&argc, &argv, MPI_THREAD_MULTIPLE, &provided);
async::Dispatcher dispatcher;
MyIOModule iomodule;
dispatcher.init();
// The communicator that should be used instead of MPI_COMM_WORLD
MPI_Comm commWorld = dispatcher.commWorld();
if (dispatcher.dispatch()) {
// Your setup goes here
iomodule.init(filename, data, numElements);
// The time loop
while (time < endtime) {
// Compute
if (doIO)
iomodule.write(time);
time += timestep;
}
// Close the file
iomodule.close();
}
dispatcher.finalize();
MPI_Finalize();
return 0;
}