MPI_Bcast a dynamic 2d array

There’s three issues here – one involving allocations, one involving where it’s allocated, and one involving how MPI works, and none of the other answers quite touch on all of them.

The first and most serious issue is where things are allocated. As correctly pointed out by @davidb, as it stands you’re allocating memory only on task zero, so the other tasks have no memory in which to recieve the broadcast.

As for 2d allocations in C in general, your code is almost exactly right. In this block of code:

     array = (float **)malloc(10*sizeof(float));
     for(i=0;i<10;i++)
         array[i] = (float *)malloc(10*sizeof(float));

the only real issue is that the first malloc should be of 10 float pointers, not floats:

     array = (float **)malloc(10*sizeof(float *));
     for(i=0;i<10;i++)
         array[i] = (float *)malloc(10*sizeof(float));

This was pointed out by @eznme. The first way may actually work depending on what memory model you’re compiling/linking with, etc, and will almost certainly work on 32-bit OSs/machines – but just because it works doesn’t always mean it’s right 🙂

Now, the final issue is that you’ve declared a perfectly good 2d array in C, but that’s not what MPI is expecting. When you make this call

MPI_Bcast(array,10*10,MPI_FLOAT,0,MPI_COMM_WORLD);

you are telling MPI to send 100 contiguous floats pointed to by array. You notice that the library routine has no way of knowing if array is the pointer to the start of a 2d or 3d or 12d array , or what the individual dimensions are; it doesn’t know if it has to follow pointers, and if it did, it wouldn’t know how many to follow.

So you want to send a float pointer to 100 contiguous floats – and in the normal C way of allocating pseudo-multidimensional arrays(*), you don’t necessarily have that. You don’t necessarily know how far away the 2nd row is from the 1st row in this layout – or even in which direction. So what you really want to do is something like this:

int malloc2dfloat(float ***array, int n, int m) {

    /* allocate the n*m contiguous items */
    float *p = (float *)malloc(n*m*sizeof(float));
    if (!p) return -1;

    /* allocate the row pointers into the memory */
    (*array) = (float **)malloc(n*sizeof(float*));
    if (!(*array)) {
       free(p);
       return -1;
    }

    /* set up the pointers into the contiguous memory */
    for (int i=0; i<n; i++) 
       (*array)[i] = &(p[i*m]);

    return 0;
}

int free2dfloat(float ***array) {
    /* free the memory - the first element of the array is at the start */
    free(&((*array)[0][0]));

    /* free the pointers into the memory */
    free(*array);

    return 0;
}

This way, and only this way, you’re guaranteed that the memory is contiguous. Then you can do

float **array;
/* ... */
malloc2dfloat(&array, 10, 10);
if (rank == 0) {
    for(i=0;i<10;i++)
         for(j=0;j<10;j++)
              array[i][j]=i+j;
}
MPI_Bcast(&(array[0][0]), 10*10, MPI_FLOAT, 0, MPI_COMM_WORLD);

Note that for an arbitrary arrangement of data, you could still do the Bcast by defining an MPI datatype which described how the 2d array is actually laid out in memory; but this is simpler and closer to what you likely actually want.

(*) the real issue here is that C and C-derived languages don’t have real multi-d arrays as first class objects – which is fine for a systems programming language, but is irredemably irritating when doing scientific programming .

Leave a Comment