Debugging MPI Programs with printf

By Junchao Zhang

What, debug with printf? Yes, though it is so basic, it works if you only want to check some variables or want to see if a specific execution path is taken. It doesn’t need any tool support, is independent of compiler flags, can be local or remote, and works with any MPI job scheduler. It is perfect when it suffices!

But with MPI, because you likely run with multiple processes, you might like to know which MPI processes printed which values, and don’t want to see interleaved output from processes. We now see how to achieve the two goals.

Tag output with MPI ranks

The easiest approach is print the MPI rank manually in the printf, e.g., printf("on rank %d: var = %d\n", myrank, var);, where you get myrank either from an MPI communicator available in the routine you inserted the printf statement, or make myrank a global variable and initialize it right after your MPI initialization statement. However, some MPI implementations provide options to tag stdout/stderr of MPI processes. Specifically,

MPI Options Description
MPICH -prepend-rank Prepend rank to output
OpenMPI -tag-output Tag all output with [job,rank]

For example, with MPICH

# MPICH
$ mpirun -n 3 -prepend-rank ./ex1
[1] Hello world from rank 1
[0] Hello world from rank 0
[2] Hello world from rank 2

and with OpenMPI

# OpenMPI
$ mpirun -n 3 -tag-output ./ex1
[1,0]<stdout>:Hello world from rank 0
[1,1]<stdout>:Hello world from rank 1
[1,2]<stdout>:Hello world from rank 2

Avoid interleaved output

If you have many printf’s and run with more than a handful of processes, it is likely their output will be interleaved on your screen, making it difficult to group output from the same process. First, think about whether you need to see output from all processes. If not, guard printf with MPI rank expressions like if (myrank == id) printf("var = %d\n", var); to only print on rank id. Actually, in PETSc, there is a utility routine PetscPrintf(MPI_Comm comm, const char format[], ...) that only print on rank 0 of comm. Otherwise, you can still leverage options from MPI implementations. Specifically,

MPI Options Description Notes
MPICH -outfile-pattern <filename>
-errfile-pattern <filename>
Direct stdout to file
Direct stderr to file
One can embed in filename %r(global MPI rank), %h (hostname), %g(process group), %p(proxy ID), e.g., %h.%r.out
OpenMPI -output-filename <dirname> Redirect output from application processes into dirname/<jobid>/rank.<id>/std[out,err].
A relative path value will be converted to an absolute path
 

For example, with MPICH

# MPICH
$ hostname
Alps-2.local

$ mpirun -n 3 -outfile-pattern %h.%r.out ./ex1

$ ls Alps-2.local.*
Alps-2.local.0.out Alps-2.local.1.out Alps-2.local.2.out

$ cat Alps-2.local.0.out
Hello world from rank 0

$ cat Alps-2.local.1.out
Hello world from rank 1

and with OpenMPI

# OpenMPI
$ mpirun -n 3 -output-filename mydir ./ex1
Hello world from rank 0
Hello world from rank 2
Hello world from rank 1

$ ls mydir/1/*
mydir/1/rank.0:
stderr  stdout

mydir/1/rank.1:
stderr  stdout

mydir/1/rank.2:
stderr  stdout

$ cat mydir/1/rank.0/stdout
Hello world from rank 0

$ cat mydir/1/rank.1/stdout
Hello world from rank 1

With that said, many printf statements could make your code messy, and later you still have to remove or comment out these printf’s. Thus if you have other means, e.g., a debugger, you should use a debugger instead to examine variables.

Tags: tools
Share: X (Twitter) Facebook LinkedIn