David's Blog

Using Message Passing Interface with Go A Guide to MPB

By David Li on 2023-05-30T19:37:46.030Z

Go is a powerful and versatile programming language, known for its simplicity, efficiency, and modern features. This has made it a popular choice for concurrent and distributed systems. In this article, we will introduce MPB (Message Passing Bridge), a library that enables the use of the Message Passing Interface (MPI) in Go programs.

MPI is a widely-used communication protocol for parallel computing that allows processes to exchange data using messages. It provides an efficient and portable way to build distributed and high-performance computing applications. By combining the power of Go and MPI through the MPB library, we can create scalable parallel solutions.

Prerequisites

To follow this guide, you should have:

  1. Go installed on your machine (version 1.16 or higher).
  2. An MPI implementation installed (e.g., OpenMPI, MPICH).
  3. Basic knowledge of Go programming and concurrent programming concepts.

Setting up MPB

To get started with MPB, you need to install the library first. You can do this using the go get command:

go get github.com/btracey/go-mpb

This command will download and install the MPB package to your Go workspace.

Writing an MPB Program

Now that we have MPB installed, let’s write a simple Go program that uses the library to send and receive messages between processes. We will create a program that distributes an array of integers among multiple processes, computes the sum of the elements in each process, and then gathers the partial sums.

Import the required packages

First, we need to import the necessary packages, including the MPB library:

package main

import (
	"fmt"
	"github.com/btracey/go-mpb"
	"github.com/btracey/go-mpb/mpbcomm"
)

Initialize and finalize MPI

Before using any MPI functions, we need to initialize the MPI environment and finalize it when we finish:

func main() {
	mpi := mpb.New(mpbcomm.New())

	err := mpi.Init()
	if err != nil {
		panic(err)
	}
	defer mpi.Finalize()
}

Get the MPI rank and size

Each process in an MPI program has a unique identifier called its rank. We also need to know the total number of processes, known as the size. We can obtain these values with the following code:

rank, err := mpi.Rank()
if err != nil {
	panic(err)
}

size, err := mpi.Size()
if err != nil {
	panic(err)
}

Distribute the data and compute the partial sums

Now, we can distribute the data and compute the partial sums. We will use the MPI_Scatter and MPI_Gather functions to distribute and gather the data, respectively. In this example, we will assume that the array has a length that is a multiple of the number of processes:

// Create the input data (only on the root process)
data := make([]int, size*4)
if rank == 0 {
	for i := range data {
		data[i] = i + 1
	}
}

// Prepare the buffers for receiving the scattered data
localData := make([]int, len(data)/size)

// Scatter the data among the processes
err = mpi.Scatter(data, localData, 0) // 0 is the root process
if err != nil {
	panic(err)
}

// Compute the local sum
localSum := 0
for _, value := range localData {
	localSum += value
}

Gather the partial sums and compute the total sum

We can now gather the partial sums and compute the total sum:

// Prepare the buffer for gathering the partial sums
partialSums := make([]int, size)

// Gather the partial sums on the root process
err = mpi.Gather(localSum, partialSums, 0) // 0 is the root process
if err != nil {
	panic(err)
}

// Compute the total sum (only on the root process)
if rank == 0 {
	totalSum := 0
	for _, sum := range partialSums {
		totalSum += sum
	}
	fmt.Printf("Total sum: %d\n", totalSum)
}

With these steps, we have created a simple Go program that uses the MPB library and MPI to distribute data and compute a sum in parallel. The complete source code is provided below:

package main

import (
	"fmt"
	"github.com/btracey/go-mpb"
	"github.com/btracey/go-mpb/mpbcomm"
)

func main() {
	mpi := mpb.New(mpbcomm.New())

	err := mpi.Init()
	if err != nil {
		panic(err)
	}
	defer mpi.Finalize()

	rank, err := mpi.Rank()
	if err != nil {
		panic(err)
	}

	size, err := mpi.Size()
	if err != nil {
		panic(err)
	}

	// Create the input data (only on the root process)
	data := make([]int, size*4)
	if rank == 0 {
		for i := range data {
			data[i] = i + 1
		}
	}

	// Prepare the buffers for receiving the scattered data
	localData := make([]int, len(data)/size)

	// Scatter the data among the processes
	err = mpi.Scatter(data, localData, 0)
	if err != nil {
		panic(err)
	}

	// Compute the local sum
	localSum := 0
	for _, value := range localData {
		localSum += value
	}

	// Prepare the buffer for gathering the partial sums
	partialSums := make([]int, size)

	// Gather the partial sums on the root process
	err = mpi.Gather(localSum, partialSums, 0)
	if err != nil {
		panic(err)
	}

	// Compute the total sum (only on the root process)
	if rank == 0 {
		totalSum := 0
		for _, sum := range partialSums {
			totalSum += sum
		}
		fmt.Printf("Total sum: %d\n", totalSum)
	}
}

Running the MPB program

To run the program, you need to compile it first. You can use the go build command to create an executable:

go build mpi_sum.go

Now you can run the program using an MPI launcher, such as mpirun or mpiexec. For example, to run the program with four processes, use the following command:

mpirun -np 4 ./mpi_sum

This will execute the program and display the total sum of the array elements.

Conclusion

In this article, we have presented an introduction to using the MPB library for Go, which enables the use of the Message Passing Interface in Go programs. With MPB, you can leverage the powerful features of both Go and MPI to create efficient and scalable parallel solutions. The example provided demonstrates how to use MPB for a simple distributed sum computation, but the library can be applied to a wide range of parallel computing tasks.

© Copyright 2024 by FriendlyUsers Tech Blog. Built with ♥ by FriendlyUser. Last updated on 2024-04-18.