How to calculate average across multiple processes using MPI_Scatter and MPI_Gather?

Viewed 974

I am trying to make a mini C program to understand MPI, I want to have the user inputing how many numbers the program will receive, the numbers, and then scatter them equally on processes. Then each process will calculate it's local average and then with the gather the root process will calculate the whole average. The numbers are stored in an array with fixed size. The problem I have is that the root process doesn't calculate the average and I don't know why. I scatter and gather the numbers as I saw in other examples but I can't make it to work. This is what I have.

#include <stdio.h>
#include "mpi.h"

int main(int argc, char** argv){
int my_rank;
int p;
int n;
int i;
int size;
int loc_num;
int loc_sum;
int fin=0;
int avg;
int loc_avg;
int root=0;
int data[100];
int data_loc[100];
int data_aver[100];
int final_res[100];

MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
MPI_Comm_size(MPI_COMM_WORLD, &p);

if (my_rank == 0){
    printf("Input how many numbers: ");
    scanf("%d", &n);
    
    printf("Input the elements of the array: ");
    for(i=0; i<n; i++){
        scanf("%d", &data[i]);
    }
}
 
MPI_Bcast(&n, 1, MPI_INT, root, MPI_COMM_WORLD);

loc_num = n/p;

MPI_Scatter(&data, loc_num, MPI_INT, data_loc, loc_num, MPI_INT, root, MPI_COMM_WORLD);


for(i=0; i< loc_num; i++){
    loc_sum += data_loc[i];
    loc_avg = loc_sum / loc_num;
}     


if(my_rank==0){

    MPI_Gather(&loc_avg, loc_num, MPI_INT, final_res, loc_num, MPI_INT, root, MPI_COMM_WORLD);

    for(i=0; i<n; i++){
        fin += final_res[i];
    }
    avg=fin/n;

    printf("Final average: %d \n", avg);
}
MPI_Finalize();
return 0;
}
1 Answers

You have some small problems with your code, namely unused variables, and variables not initialized. You should compile your code with some warning flags such as -Wall and -pedantic, among others. Those, flags warning the user about the aforementioned problems.

Furthermore, you can change this:

for(i=0; i< loc_num; i++){
    loc_sum += data_loc[i];
    loc_avg = loc_sum / loc_num;
}  

to

for(i=0; i< loc_num; i++){
    loc_sum += data_loc[i];
}   
loc_avg = loc_sum / loc_num;

You need to initialize the variable loc_sum with zero, since the first time the code interacts with that variable is to perform loc_sum += data_loc[i];, otherwise is undefined behavior.

And since the average can be a floating number, the data type of the variables loc_avg , avg, and fin should be a float as a type (or double) instead of int.

You are calling the MPI_Gather only by the master:

if(my_rank==0){

    MPI_Gather(&loc_avg, loc_num, MPI_INT, final_res, loc_num, MPI_INT, root, MPI_COMM_WORLD);

    for(i=0; i<n; i++){
        fin += final_res[i];
    }
    avg=fin/n;

    printf("Final average: %d \n", avg);
}

MPI_Gather is a collect communication directive, it should be called by all the processes within the group. Moreover, you are calling MPI_Gather with the wrong parameters, instead of :

MPI_Gather(&loc_avg, loc_num, MPI_INT, final_res, loc_num, MPI_INT, root, MPI_COMM_WORLD);

called with

MPI_Gather(&loc_avg, 1, MPI_FLOAT, final_res, 1, MPI_FLOAT, root, MPI_COMM_WORLD);

This loc_num = n/p; only works fine when the input can be evenly divided among processes, but (for instance) if you have an input of 10 and 4 processes loc_num = n/p; would be equal to 2, so each process will compute 2 of 10. Hence, all processes will compute 8 instead of 10. This one I will leave it you to solve. Alternatively, you can just assume that n/p produces always a natural number.

Lastly, instead of:

for(i=0; i<n; i++){
    fin += final_res[i];
}
 avg=fin/n;

you should have

for(i=0; i<p; i++){
    fin += final_res[i];
} 
 avg=fin/p;

You want to sum the local average of all processes, hence use the total of number of processes "p", and not the size of the input array "n".

Some advices:

  1. if you can, declare the variable as close as possible to the code where they are being used;
  2. give better name to your variables, for instance, instead of "n" and "p", called input_size and "total_processes", respectively.

IMO this improves the code readability. With all these change your code will look like:

#include <stdio.h>
#include "mpi.h"

int main(int argc, char** argv){
    int my_rank;
    int total_processes;
    int root = 0;
    int data[100];
    int data_loc[100];
    float final_res[100];

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    MPI_Comm_size(MPI_COMM_WORLD, &total_processes);

    int input_size = 0;
    if (my_rank == 0){
       printf("Input how many numbers: ");
       scanf("%d", &input_size);
    
       printf("Input the elements of the array: ");
       for(int i=0; i<input_size; i++){
           scanf("%d", &data[i]);
       }
    }
 
    MPI_Bcast(&input_size, 1, MPI_INT, root, MPI_COMM_WORLD);

    int loc_num = input_size/total_processes;

    MPI_Scatter(&data, loc_num, MPI_INT, data_loc, loc_num, MPI_INT, root, MPI_COMM_WORLD);

    int loc_sum = 0;
    for(int i=0; i< loc_num; i++)
        loc_sum += data_loc[i];     
    float loc_avg = (float) loc_sum / (float) loc_num;
    MPI_Gather(&loc_avg, 1, MPI_FLOAT, final_res, 1, MPI_FLOAT, root, MPI_COMM_WORLD);

    if(my_rank==0){
      float fin = 0;
      for(int i=0; i<total_processes; i++)
         fin += final_res[i];
      float avg = fin / (float) total_processes;
      printf("Final average: %f \n", avg);
    }
    MPI_Finalize();
    return 0;
}

Test input : 10 elements, with the elements being {1,2,3,4,5,6,7,8}

Result :

PROCESS=0 loc_avg=1.5 {1, 2} 
PROCESS=1 loc_avg=3.5 {3, 4}
PROCESS=2 loc_avg=5.5 {5, 6}
PROCESS=3 loc_avg=7.5 {7, 8}

Final average: 4.500000
Related