Skip to content
Jonathan Beard edited this page Nov 20, 2018 · 6 revisions

Building Kernels

All kernels within RaftLib extend the base raft::kernel class. It defines an interface for building more advanced objects. It cannot be instantiated as is. For one, it wouldn't be very interesting, and two it contains pure virtual functions which the compiler should complain about if no implementation exists. Lets take a look at what an extension should look like:

#include <raft>

class someclass : public raft::kernel
{
public:
    someclass()
    {
       /** 
        * you'll want to add ports, that's how
        * you communicate with the outside world
        */
       input.addPort< int >( "a" /** name them whatever you like **/);
       /** you can even chain like-typed ports in one invocation **/
       output.addPort< float >( "a" /** if you want, you can have same name as input **/, "b" );
       /** 
        * NOTE: names must be unique for each "side" of the kernel, i.e., across the 
        * input space all names must be unique, and across output as well, but output
        * can have the same name defined that exists within the input.
        */
    }
    virtual ~someclass() = default;

    virtual raft::kstatus run()
    {
       /** this is where you'll access ports, and do work **/
       /** 
        * There are two return types:
        * raft::proceed - usual return when you're not done, if you're
        * not a producer kernel (explained next), this should be the 
        * default return
        *
        * raft::stop - if you're a producer kernel (i.e., you have
        * output only, no input, you'll need to send this when you're
        * totally done producing output, otherwise the scheduler won't
        * know when to exit the application.
        */
       return( raft::proceed );
    }
};

A variant of the kernel base class that is designed so that the programmer can specify to the runtime that data must be available on all input ports before activating the kernel is raft::kernel_all. If instead of raft::kernel the programmer extends the all variant. Future versions of the all kernel will be a template which will enable the kernel programmer to specify the number of input items for the kernel activation to wait on statically.

There's another kernel type that can be extended that is designed for parallelizing producers or consumers, i.e. if you have an output or input that doesn't care about ordering, then you can extend raft::parallel_k in order to let the scheduler know that it can add input ports at will (dynamically), without disrupting the output flow of data, or the correctness of the application. Below is an example of extending the parallel_k class. This is one of the very cool things about the RaftLib framework, the graph isn't static. It is continually monitored, and performance improved while it executes.

#include <raft>

class someclass : public raft::parallel_k
{
public:
    someclass()
    {
       /** 
        * initialize a single port, you'll only want to add output or
        * input ports, future versions will throw an exception for adding
        * both. Using the addPortTo function will add a port to the 
        * specified side, and set the name to be "0" for the first one.
        * subsequent ports are named sequentially.
        */
       addPortTo< int >( output );
    }
    virtual ~someclass() = default;

    virtual raft::kstatus run()
    {
       /** caveats same as the example above **/
       /**
        * NOTE: you'll want to use the ports using an iterator, the
        * iterator locks the structure containing the ports so that
        * the run-time can't add or delete ports while you're using
        * them. Since we've declared this kernel to have only output
        s we need to iterate over the output structure
        */
       for( auto &port : output )
       {
          //do something with each port
       }
       return( raft::proceed );
    }
};

Enabling cloning and parallelization.

First, you'll need to make a copy constructor for your kernel (class). Second, you'll need to add the CLONE() macro which inserts some C++ code within each compilation unit so your kernel can be cloned by the run-time without knowing specifically what the original arguments were, or even have specific type information about it. The reason it is defined as a macro is that the specific C++ code needs to be placed within each calling compilation unit, not derived from any other class's virtual functions (the base class kernel.hpp defines a clone() function which if invoked will assert false and cause the program to exit). Currently that seems to be the only way to do this within C++ (although if you have a way, let me know).

Lambda compute kernels

#include <raft>
#include <raftio>

#include <iostream>
#include <cstdlib>
#include <cstdint>
#include <random>

int
main( int argc, char **argv )
{
    using type_t = std::int32_t;
    using print  = raft::print< type_t, '\n' >;
    using rnd    = raft::lambdak< type_t >;

    /** instantiate a random number generator via lambda kernel **/
    rnd lk( /** input ports     **/ 0, 
            /** output ports    **/ 1, 
            /** the kernel func **/
        [&]( Port &input,
             Port &output )
        {
            static std::default_random_engine generator;
            static std::uniform_int_distribution< type_t > distribution(1,10);
            static auto rand_func = std::bind( distribution,  generator ); 
            static std::size_t gen_count( 0 );
            if( gen_count++ < 10000 )
            {
               output[ "0" ].push( 
                  rand_func(),
                  raft::none );
               return( raft::proceed );
            }
            return( raft::stop );
        } );

    print p;
    raft::map m;
    m += lk >> p;
    m.exe();
    return( EXIT_SUCCESS );
}