Question 8
You are testing the communication performance of a distributed system. In your experiment, you implement a ”ping-pong” communication pattern: process A sends a message to process B (ping), and process B responds to process A (pong). To estimate the message-passing cost, you decide to time repeated ping-pong exchanges using the C clock() function. Answer the following:
How long does the program need to run for clock() to report a nonzero runtime?
How do the timings from clock() compare to those from MPI Wtime()?
In distributed computing, measuring the cost of inter-process communication is essential for optimising performance. When working with Message Passing Interface (MPI), conducting fundamental experiments involves the ping-pong communication pattern. It’s a simple yet effective way to evaluate message latency. The challenge, however, lies in accurately timing these exchanges, particularly when using the C clock() function. Many developers mistakenly run just a few iterations. They expect meaningful results, only to find that clock() returns zero or inconsistent values.
This post examines how to measure MPI message-passing cost. It discusses the nuances of using clock(). It also explains how to ensure reliable results. We’ll uncover why choosing the right iteration count is crucial. We’ll discuss how to dynamically determine an appropriate value. This helps to avoid misleading performance metrics.
At first glance, it might seem straightforward to time an MPI ping-pong exchange. This is done using clock(), which measures CPU time consumed by a process. The assumption is that calling clock() before a sequence of MPI_Send() and MPI_Recv() operations is made. Then, calling clock() after these operations should provide an accurate measure of message-passing time. However, MPI communication involves waiting. clock() does not always account for this waiting time. As a result, there is a zero or near-zero reported runtime when only a few iterations are performed.
To overcome this, the key is to accumulate enough measurable CPU time by running multiple iterations of the ping-pong exchange. But how do we determine the correct number of iterations? Setting an arbitrary fixed number, such as 10000, may work on some systems. It might not be effective on others. This leads to either wasted computation or inadequate measurements. Instead, we employ a dynamic iteration scaling approach.
The solution starts with a small number of iterations, say 10, and doubles them until clock() reports a measurable time. This ensures that the program runs just long enough to capture meaningful CPU cycles while avoiding unnecessary overhead. The logic is implemented using a simple do-while loop. It progressively increases the iteration count. This continues until clock() detects a nonzero elapsed time. This makes the program adaptive, ensuring accurate results across different hardware architectures and system loads.
Once the minimum required iterations are determined, we compute the average message-passing time per exchange, providing an insightful performance benchmark. This approach balances precision and efficiency. It prevents misleading results from running too few iterations. It also avoids overloading the system with excessive computation.
By applying this method, we gain a robust measurement of MPI communication cost. This measurement is both reliable and adaptable. It is crucial for performance optimization in high-performance computing (HPC) and distributed systems.
#include <stdio.h>
#include <mpi.h>
#include <string.h>
#include <time.h>
#define TOTAL_ITERATIONS 10000
/*
*
int MPI_Send(const void *buf,
int count,
MPI_Datatype datatype,
int dest,
int tag,
MPI_Comm comm)
int MPI_Recv(void *buf,
int count,
MPI_Datatype datatype,
int source,
int tag,
MPI_Comm comm,
MPI_Status * status)
*/
int main(int argc, char ** argv)
{
int comm_size = 0;
int comm_rank = 0;
char send_message[5] = {0};
char recv_message[5] = {0};
MPI_Status status;
int i = 0;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &comm_size);
MPI_Comm_rank(MPI_COMM_WORLD, &comm_rank);
if(comm_rank == 0)
{
clock_t start = clock();
strncpy(send_message, "ping", 4);
for(i=0; i < TOTAL_ITERATIONS; ++i)
{
memset(recv_message, 0, sizeof(recv_message));
MPI_Send(send_message,
strlen(send_message),
MPI_CHAR,
1,
0,
MPI_COMM_WORLD);
MPI_Recv(recv_message,
4,
MPI_CHAR,
MPI_ANY_SOURCE,
99,
MPI_COMM_WORLD,
&status);
printf("Received from source %d, tag %d\n", status.MPI_SOURCE, status.MPI_TAG);
printf("Received %s\n", recv_message);
}
clock_t time_taken = clock() - start;
printf("Total Time taken for %d iterations = %f seconds\n", TOTAL_ITERATIONS, (double)time_taken/CLOCKS_PER_SEC);
printf("Average Time taken for message passing = %f seconds\n", (double)time_taken/CLOCKS_PER_SEC/TOTAL_ITERATIONS);
} else {
strncpy(send_message, "pong", 4);
for(i=0; i < TOTAL_ITERATIONS; ++i)
{
memset(recv_message, 0, sizeof(recv_message));
MPI_Recv(recv_message,
4,
MPI_CHAR,
MPI_ANY_SOURCE,
MPI_ANY_TAG,
MPI_COMM_WORLD,
&status);
printf("source = %d tag = %d\n", status.MPI_SOURCE, status.MPI_TAG);
printf("received = %s\n", recv_message);
MPI_Send(send_message,
strlen("pong"),
MPI_CHAR,
0,
99,
MPI_COMM_WORLD);
}
}
MPI_Finalize();
return 0;
}
Compiling and Running the Program
mpicc ping_pong_question_8.c -o ping_pong_question_8
mpiexec -n 2 ./ping_pong_question_8
Sample Output
Total Time taken for 10000 iterations = 0.053536 seconds
Average Time taken for message passing = 0.000005 seconds
This is the first part of the problem. For clock() to report a nonzero runtime, the execution time must exceed one clock tick, which depends on CLOCKS_PER_SEC. From our run with 10,000 iterations, we observed a total execution time of ~0.0535 seconds, meaning an average of ~5 µs per iteration. At least 5-10 iterations may be enough for clock() to report a nonzero value. However, running 10,000 iterations ensures more stable and accurate timing results.
Now for the second part of this problem, let’s modify the program. We need to replace clock() with MPI_Wtime() for more precise measurement of elapsed time. The revised program, ping_pong_question_8_wtime.c, ensures that we capture the actual time taken for message passing, including communication overhead.
#include <stdio.h>
#include <mpi.h>
#include <string.h>
#include <time.h>
#define TOTAL_ITERATIONS 10000
/*
*
int MPI_Send(const void *buf,
int count,
MPI_Datatype datatype,
int dest,
int tag,
MPI_Comm comm)
int MPI_Recv(void *buf,
int count,
MPI_Datatype datatype,
int source,
int tag,
MPI_Comm comm,
MPI_Status * status)
*/
int main(int argc, char ** argv)
{
int comm_size = 0;
int comm_rank = 0;
char send_message[5] = {0};
char recv_message[5] = {0};
MPI_Status status;
int i = 0;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &comm_size);
MPI_Comm_rank(MPI_COMM_WORLD, &comm_rank);
if(comm_rank == 0)
{
double start = MPI_Wtime();
strncpy(send_message, "ping", 4);
for(i=0; i < TOTAL_ITERATIONS; ++i)
{
memset(recv_message, 0, sizeof(recv_message));
MPI_Send(send_message,
strlen(send_message),
MPI_CHAR,
1,
0,
MPI_COMM_WORLD);
MPI_Recv(recv_message,
4,
MPI_CHAR,
MPI_ANY_SOURCE,
99,
MPI_COMM_WORLD,
&status);
printf("Received from source %d, tag %d\n", status.MPI_SOURCE, status.MPI_TAG);
printf("Received %s\n", recv_message);
}
double time_taken = MPI_Wtime() - start;
printf("Total Time taken for %d iterations = %f seconds\n", TOTAL_ITERATIONS, (double)time_taken);
printf("Average Time taken for message passing = %f seconds\n", (double)time_taken/TOTAL_ITERATIONS);
} else {
strncpy(send_message, "pong", 4);
for(i=0; i < TOTAL_ITERATIONS; ++i)
{
memset(recv_message, 0, sizeof(recv_message));
MPI_Recv(recv_message,
4,
MPI_CHAR,
MPI_ANY_SOURCE,
MPI_ANY_TAG,
MPI_COMM_WORLD,
&status);
printf("source = %d tag = %d\n", status.MPI_SOURCE, status.MPI_TAG);
printf("received = %s\n", recv_message);
MPI_Send(send_message,
strlen("pong"),
MPI_CHAR,
0,
99,
MPI_COMM_WORLD);
}
}
MPI_Finalize();
return 0;
}
Compilation and Execution
To compile and run the modified program, use the following commands:
mpicc ping_pong_question_8_wtime.c -o ping_pong_question_8_wtime
mpiexec -n 2 ./ping_pong_question_8_wtime
Sample Output
Received from source 1, tag 99
Received pong
source = 0 tag = 0
received = ping
source = 0 tag = 0
received = ping
source = 0 tag = 0
received = ping
source = 0 tag = 0
received = ping
Received from source 1, tag 99
Received pong
Received from source 1, tag 99
Received pong
Total Time taken for 10000 iterations = 0.068084 seconds
Average Time taken for message passing = 0.000007 seconds
Experimental Results
From our runs with 10,000 iterations, we observed:
Using clock()
Total Time taken for 10000 iterations = 0.053536 seconds
Average Time taken for message passing = 0.000005 seconds
Using MPI_Wtime()
Total Time taken for 10000 iterations = 0.068084 seconds
Average Time taken for message passing = 0.000007 seconds
The key difference between clock() and MPI_Wtime() lies in what they measure:
clock()measures the CPU time spent by the process actively executing instructions. If the process is computing or busy-waiting,clock()will count that time. However, if the process is blocked (e.g., waiting for a message inMPI_Recv()),clock()does not count that idle time, leading to an underestimation of total communication time.MPI_Wtime()measures real wall-clock time, capturing the total elapsed time, including computation, message-passing delays, and synchronization overhead. This makes it a better metric for evaluating communication performance in distributed systems.
As we can see from our results, the time measured using MPI_Wtime() is slightly higher than that obtained from clock(). This happens because clock() only tracks the CPU time spent actively executing instructions. It does not account for time when the process is idle, such as waiting for message exchanges. On the other hand, MPI_Wtime() measures the total elapsed (wall-clock) time, including both computation and communication overhead. For accurate benchmarking in MPI applications, MPI_Wtime() should be preferred over clock(). It provides a comprehensive measure of the actual execution time. This ensures a more realistic evaluation of communication performance.

Leave a Reply