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 &param)
    {
        // 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 &param)
    {
        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;
}
⚠️ **GitHub.com Fallback** ⚠️