ledatelescope / bifrost

A stream processing framework for high-throughput applications.
BSD 3-Clause "New" or "Revised" License
64 stars 29 forks source link

Update C++ API documentation #184

Open telegraphic opened 1 year ago

telegraphic commented 1 year ago

Hi all,

I've been exploring the C++ API a bit recently, and have found it a bit hard going as there's not much documentation (and my C++ needs more time in the dojo).

There is a great little example here already though, on how to use the ring interface: http://ledatelescope.github.io/bifrost/Cpp-Development.html

So I thought we could add a few more examples?

Here's an example code that shows how to use bifrost arrays in C++. The code compiles and does what it's supposed to do, but is simple (on purpose, but if there's 'better' ways lemme know).

I have some questions in the comments, would be good if someone could help fill these in!

(PS: It would also be good if we had these in an examples directory/repo, that the user can compile)

#include <bifrost/array.h>
#include <bifrost/common.h>
#include <bifrost/ring.h>
#include <utils.hpp>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>

// Simple 'kernel' to add two arrays together, into a third array
BFstatus AddStuff(BFarray *xdata, BFarray *ydata, BFarray *sumdata)
{
    long nelements = num_contiguous_elements(xdata);

    float* x = (float *)xdata->data;
    float* y = (float *)ydata->data;
    float* summed = (float *)sumdata->data;

    for(int i=0; i < nelements; i +=1)
    {
       summed[i] = x[i] + y[i];
    }

    return BF_STATUS_SUCCESS;
}

int main() {

    // Create input and output arrays
    BFarray bf_data_x;
    BFarray bf_data_y;
    BFarray bf_data_sum;

    int N = 16;  // 16 samples in array

    // dtype codes are set in array.h
    // https://github.com/ledatelescope/bifrost/blob/master/src/bifrost/array.h#L42
    // space codes are set in memory.h
    // https://github.com/ledatelescope/bifrost/blob/a57a83f8bffc91feed5146d7264a72e0c8ddeb6d/src/bifrost/memory.h#L43
    bf_data_x.dtype = BF_DTYPE_I32;
    bf_data_x.space = BF_SPACE_SYSTEM;
    bf_data_x.ndim = 1;
    bf_data_x.shape[0] = N;

    //BFstatus codes are set in https://github.com/ledatelescope/bifrost/blob/master/src/bifrost/common.h#L54
    //BF_CHECK checks codes, the error codes are found in assert.hpp
    //https://github.com/ledatelescope/bifrost/blob/master/src/assert.hpp#L146

    BF_CHECK(bfArrayMalloc(&bf_data_x));

    // Fill with test vector 
    int* data_x = (int *)bf_data_x.data;
    for(int i=0; i < N; i++) {
        data_x[i] = i;
    }

    // Repeat for Y data arrray
    bf_data_y.dtype = BF_DTYPE_I32;
    bf_data_y.space = BF_SPACE_SYSTEM;
    bf_data_y.ndim = 1;
    bf_data_y.shape[0] = N;
    BF_CHECK(bfArrayMalloc(&bf_data_y));

    // Fill with test vector - set all to 1
    int* data_y = (int *)bf_data_y.data;
    for(int i=0; i < N; i++) {
        data_y[i] = 1;
    }

    // Repeat for Y data arrray
    bf_data_sum.dtype = BF_DTYPE_I32;
    bf_data_sum.space = BF_SPACE_SYSTEM;
    bf_data_sum.ndim = 1;
    bf_data_sum.shape[0] = N;
    BF_CHECK(bfArrayMalloc(&bf_data_sum));
    int* data_sum = (int *)bf_data_sum.data;

    // run the AddStuff function
    BF_CHECK(AddStuff(&bf_data_x, &bf_data_y, &bf_data_sum));

    // Print some stuff to screen
     for(int i=0; i < N; i++) {
        cout << "idx[" << i << "]: " << data_x[i] << " + " << data_y[i] << " = " << data_sum[i] << endl;
        // Is this the right way to use BF_ASSERT
        BF_ASSERT( data_x[i] + data_y[i] == data_sum[i], BF_STATUS_INTERNAL_ERROR);
    }   

    // Free memory
    BF_CHECK(bfArrayFree(&bf_data_x));
    BF_CHECK(bfArrayFree(&bf_data_y));
    BF_CHECK(bfArrayFree(&bf_data_sum));

    // TODO: what does bfArrayMemset do? (Show in example)
    // TODO: Show bfArrayCopy in another example 
    // TODO: show dtype2ctype_string from array_util

    // TODO: What's the deal with check() in Common.hpp?
    // https://github.com/ledatelescope/bifrost/blob/a57a83f8bffc91feed5146d7264a72e0c8ddeb6d/src/bifrost/Common.hpp#L44

    // TODO: What's the deal with _check in the python wrappers?
    // Should I be using this in my Python plugins?

    // Note: Am I using BF_CHECK correctly? 
    // Note: looks like BF_ASSERT is more common than BF_CHECK? What's the difference?
    // https://github.com/ledatelescope/bifrost/blob/master/src/assert.hpp#L102
    // TODO: Another tutorial on error handling

    // TODO: example with CUDA in it
    // TODO: What's the deal with ArrayIndexer.cuh and ShapeIndexer.cuh? 
}
jaycedowell commented 1 year ago

I think having more examples, particularly on the C++ side, is a great idea. I'll see if I can find some time to dig into you questions.

jaycedowell commented 1 year ago

// TODO: What's the deal with check() in Common.hpp?

I'm not sure. It looks to be similar to the BF_CHECK macro except that it raises an exception on an error rather than just printing a message and passing the return value along.

// TODO: What's the deal with _check in the python wrappers? // Should I be using this in my Python plugins?

_check in Python either catches some particular kinds of non-BF_STATUS_SUCCESS return values and converts them to Python exceptions. The exact behavior is determined by whether or not the Python constant __debug__ is True or False.

// Note: Am I using BF_CHECK correctly?

Looks like it. As an alternative you could also use the more C-like:

if( !bfArrayMalloc(&bf_data_y) ) {
  printf("bfArrayMalloc failed\n");
}

since BF_STATUS_SUCCESS is zero.

// Note: looks like BF_ASSERT is more common than BF_CHECK? What's the difference?

BF_ASSERT throws exceptions in from inside a class method. I think of it as a part of the input validate step in a method, similar to what you would do in Python:

def my_op(input):
  assert(input.shape == 2)
  input[:,0] += 1
  return input

vs.

void my_op(BFarray* input) {
  BF_ASSERT(input->ndim == 2, BF_STATUS_INVALID_SHAPE);
  for(int i=0; i<input->shape[0]; i++) {
    input->data[i*input->shape[1]] += 1;
 }
}

(I hope that's the correct syntax there).

BF_CHECK is meant more for check the return code of a method. My Python example here would be it's like checking the return code on scipy.optimize.leastsq.