From 4ca3338c0db9747c3a3810cd89b4d685b06eb72e Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Fri, 22 May 2026 19:06:06 +0200 Subject: [PATCH 01/26] Restructure, add benchmarks, migrate to CMake Bug fixes: - Fix duplicate Irecv/Send pair in o2o_bsnbr (was posting each op twice) - Fix MPI_STATUS_IGNORE -> MPI_STATUSES_IGNORE in all Waitall calls - Fix checker.c log filename (was using locale timestamp, now checker_YYYYMMDD_HHMMSS.log) - Fix spurious 'receiver rank' in alltoall/allreduce print strings - Fix null_dummy.c to properly call MPI_Init/Finalize - Add MPI_Init/Finalize to null_dummy New benchmarks: - allgather_b/nb (MPI_Allgather / MPI_Iallgather) - gather_b/nb (MPI_Gather / MPI_Igather) - allreduce_b/nb (MPI_Allreduce / MPI_Iallreduce, renamed from ardc) - broadcast_b/nb (MPI_Bcast / MPI_Ibcast, renamed from bdc) - reduce_b/nb (MPI_Reduce / MPI_Ireduce) - scatter_b/nb (MPI_Scatter / MPI_Iscatter) - reduce_scatter_b/nb (MPI_Reduce_scatter / MPI_Ireduce_scatter) - barrier_nb (MPI_Ibarrier) - pairwise_b (MPI_Sendrecv one-to-one, renamed from o2o) - stencil_2d_nb: 2D Cartesian halo exchange via MPI_Cart_create - kpartners_nb: k-regular random traffic (-k flag); k=1 ~ pairwise, k=N-1 ~ alltoall Removed: - bursty_noise_a2a.cpp and bursty_noise_incast.cpp (redundant: -endl -blength -bpause flags on standard benchmarks cover the same use case) Restructure: - All sources moved under src/ with one subdirectory per traffic pattern - Files renamed to full descriptive names (agtr->allgather, ardc->allreduce, bdc->broadcast, inc->incast, o2o->pairwise, a2a->alltoall, barrier->barrier_b) - common.h moved to src/ Build system: - Replace Makefile with CMake (minimum 3.16) - src/CMakeLists.txt uses GLOB_RECURSE CONFIGURE_DEPENDS: adding a new .c/.cpp file requires zero CMake edits - Add .gitignore (build/) Co-Authored-By: Claude Sonnet 4.5 --- .gitignore | 1 + CMakeLists.txt | 17 + Makefile | 34 -- blink.png | Bin 0 -> 28907 bytes blink.svg | 334 ++++++++++++++++++ bursty_noise_a2a.cpp | 129 ------- bursty_noise_incast.cpp | 96 ----- null_dummy.c | 1 - src/CMakeLists.txt | 22 ++ src/allgather/allgather_b.c | 185 ++++++++++ .../allgather/allgather_comm_only.cpp | 8 +- src/allgather/allgather_nb.c | 189 ++++++++++ ardc_b.c => src/allreduce/allreduce_b.c | 8 +- ardc_nb.c => src/allreduce/allreduce_nb.c | 8 +- a2a_b.c => src/alltoall/alltoall_b.c | 0 .../alltoall/alltoall_comm_only.cpp | 0 a2a_man.c => src/alltoall/alltoall_man.c | 8 +- a2a_nb.c => src/alltoall/alltoall_nb.c | 0 barrier.c => src/barrier/barrier_b.c | 0 src/barrier/barrier_nb.c | 170 +++++++++ bdc_b.c => src/broadcast/broadcast_b.c | 0 bdc_nb.c => src/broadcast/broadcast_nb.c | 0 common.h => src/common.h | 0 src/gather/gather_b.c | 189 ++++++++++ src/gather/gather_nb.c | 193 ++++++++++ inc_b.c => src/incast/incast_b.c | 0 inc_bsnbr.c => src/incast/incast_bsnbr.c | 0 inc_get.c => src/incast/incast_get.c | 0 inc_nb.c => src/incast/incast_nb.c | 0 inc_put.c => src/incast/incast_put.c | 0 src/kpartners/kpartners_nb.c | 245 +++++++++++++ src/pairwise/pairwise_b.c | 220 ++++++++++++ o2o_bsnbr.c => src/pairwise/pairwise_bsnbr.c | 10 +- o2o_nb.c => src/pairwise/pairwise_nb.c | 8 +- ping-pong_b.c => src/pingpong/pingpong_b.c | 0 .../pingpong/pingpong_pairwise_b.c | 0 src/reduce/reduce_b.c | 191 ++++++++++ src/reduce/reduce_nb.c | 195 ++++++++++ src/reduce_scatter/reduce_scatter_b.c | 200 +++++++++++ src/reduce_scatter/reduce_scatter_nb.c | 204 +++++++++++ ring_bsnbr.c => src/ring/ring_bsnbr.c | 2 +- ring_nb.c => src/ring/ring_nb.c | 4 +- src/scatter/scatter_b.c | 187 ++++++++++ src/scatter/scatter_nb.c | 191 ++++++++++ src/stencil/stencil_2d_nb.c | 247 +++++++++++++ checker.c => src/tools/checker.c | 10 +- src/tools/null_dummy.c | 6 + 47 files changed, 3219 insertions(+), 293 deletions(-) create mode 100644 .gitignore create mode 100644 CMakeLists.txt delete mode 100644 Makefile create mode 100644 blink.png create mode 100644 blink.svg delete mode 100644 bursty_noise_a2a.cpp delete mode 100644 bursty_noise_incast.cpp delete mode 100644 null_dummy.c create mode 100644 src/CMakeLists.txt create mode 100644 src/allgather/allgather_b.c rename agtr_comm_only.cpp => src/allgather/allgather_comm_only.cpp (96%) create mode 100644 src/allgather/allgather_nb.c rename ardc_b.c => src/allreduce/allreduce_b.c (94%) rename ardc_nb.c => src/allreduce/allreduce_nb.c (94%) rename a2a_b.c => src/alltoall/alltoall_b.c (100%) rename a2a_comm_only.cpp => src/alltoall/alltoall_comm_only.cpp (100%) rename a2a_man.c => src/alltoall/alltoall_man.c (95%) rename a2a_nb.c => src/alltoall/alltoall_nb.c (100%) rename barrier.c => src/barrier/barrier_b.c (100%) create mode 100644 src/barrier/barrier_nb.c rename bdc_b.c => src/broadcast/broadcast_b.c (100%) rename bdc_nb.c => src/broadcast/broadcast_nb.c (100%) rename common.h => src/common.h (100%) create mode 100644 src/gather/gather_b.c create mode 100644 src/gather/gather_nb.c rename inc_b.c => src/incast/incast_b.c (100%) rename inc_bsnbr.c => src/incast/incast_bsnbr.c (100%) rename inc_get.c => src/incast/incast_get.c (100%) rename inc_nb.c => src/incast/incast_nb.c (100%) rename inc_put.c => src/incast/incast_put.c (100%) create mode 100644 src/kpartners/kpartners_nb.c create mode 100644 src/pairwise/pairwise_b.c rename o2o_bsnbr.c => src/pairwise/pairwise_bsnbr.c (93%) rename o2o_nb.c => src/pairwise/pairwise_nb.c (96%) rename ping-pong_b.c => src/pingpong/pingpong_b.c (100%) rename pw-ping-pong_b.c => src/pingpong/pingpong_pairwise_b.c (100%) create mode 100644 src/reduce/reduce_b.c create mode 100644 src/reduce/reduce_nb.c create mode 100644 src/reduce_scatter/reduce_scatter_b.c create mode 100644 src/reduce_scatter/reduce_scatter_nb.c rename ring_bsnbr.c => src/ring/ring_bsnbr.c (99%) rename ring_nb.c => src/ring/ring_nb.c (99%) create mode 100644 src/scatter/scatter_b.c create mode 100644 src/scatter/scatter_nb.c create mode 100644 src/stencil/stencil_2d_nb.c rename checker.c => src/tools/checker.c (96%) create mode 100644 src/tools/null_dummy.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..567609b12 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..3f658bb8e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.16) +project(blink C CXX) + +find_package(MPI REQUIRED COMPONENTS C CXX) + +# All binaries land in /bin/ +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +# Default to Release so -O3 is active unless the user overrides +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) +endif() + +add_compile_options(-O3) +add_compile_definitions(_GNU_SOURCE) + +add_subdirectory(src) diff --git a/Makefile b/Makefile deleted file mode 100644 index c65649e8f..000000000 --- a/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -# Directories -SRC_DIR = . -BIN_DIR = bin - -CC = mpicc -CXX = mpicxx - -# Source files -C_SRCS = $(wildcard $(SRC_DIR)/*.c) -CPP_SRCS = $(wildcard $(SRC_DIR)/*.cpp) - -# Executable names -C_BINS = $(patsubst $(SRC_DIR)/%.c, $(BIN_DIR)/%, $(C_SRCS)) -CPP_BINS = $(patsubst $(SRC_DIR)/%.cpp, $(BIN_DIR)/%, $(CPP_SRCS)) - -# Compilation flags -CFLAGS = -lm -D_GNU_SOURCE -O3 -CXXFLAGS = -lm -D_GNU_SOURCE -O3 # Matches CFLAGS, but you can add C++ specific flags like -std=c++17 here - -# Targets -all: $(C_BINS) $(CPP_BINS) - -# Rule for C files -$(BIN_DIR)/%: $(SRC_DIR)/%.c $(SRC_DIR)/common.h - @mkdir -p $(BIN_DIR) - $(CC) -o $@ $< $(CFLAGS) - -# Rule for C++ files -$(BIN_DIR)/%: $(SRC_DIR)/%.cpp $(SRC_DIR)/common.h - @mkdir -p $(BIN_DIR) - $(CXX) -o $@ $< $(CXXFLAGS) - -clean: - -rm -f $(C_BINS) $(CPP_BINS) diff --git a/blink.png b/blink.png new file mode 100644 index 0000000000000000000000000000000000000000..0a828151ca7182c94a89e69b1deabd6444053d33 GIT binary patch literal 28907 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BLINK + + + diff --git a/bursty_noise_a2a.cpp b/bursty_noise_a2a.cpp deleted file mode 100644 index 30cabb556..000000000 --- a/bursty_noise_a2a.cpp +++ /dev/null @@ -1,129 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include "common.h" - - -void all2all_memcpy(const void* sendbuf, int sendcount, MPI_Datatype sendtype, void* recvbuf, int recvcount, MPI_Datatype recvtype, MPI_Comm comm){ - - int rank, size; - MPI_Comm_rank(comm, &rank); - MPI_Comm_size(comm, &size); - - int datatype_size; - MPI_Type_size(sendtype, &datatype_size); - - const char* sbuf = static_cast(sendbuf); - char* rbuf = static_cast(recvbuf); - - double mem_time = MPI_Wtime(); - // Copy local data directly (self-send) - std::memcpy(rbuf + rank * datatype_size * recvcount, - sbuf + rank * datatype_size * sendcount, - sendcount * datatype_size); - -} - -void custom_alltoall(const void* sendbuf, int sendcount, MPI_Datatype sendtype, - void* recvbuf, int recvcount, MPI_Datatype recvtype, MPI_Comm comm) { - int rank, size; - MPI_Comm_rank(comm, &rank); - MPI_Comm_size(comm, &size); - - int datatype_size; - MPI_Type_size(sendtype, &datatype_size); - - const char* sbuf = static_cast(sendbuf); - char* rbuf = static_cast(recvbuf); - - std::vector requests; - for (int i = 0; i < size; ++i) { - if (i == rank) continue; - - MPI_Request req_recv; - MPI_Request req_send; - - MPI_Isend(sbuf + i * datatype_size * sendcount, sendcount, sendtype, i, 0, comm, &req_send); - MPI_Irecv(rbuf + i * datatype_size * recvcount, recvcount, recvtype, i, 0, comm, &req_recv); - - requests.push_back(req_send); - requests.push_back(req_recv); - } - - MPI_Waitall(static_cast(requests.size()), requests.data(), MPI_STATUSES_IGNORE); -} - -int main(int argc, char** argv) { - MPI_Init(&argc, &argv); - - int size, rank; - MPI_Comm_size(MPI_COMM_WORLD, &size); - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - - const size_t BUFFER_SIZE = 2 * 1024 * 1024; // bytes per peer 2MiB - - // Each process will send a chunk to every other process - unsigned char *send_buffer = (unsigned char*) malloc(BUFFER_SIZE*size); - unsigned char *recv_buffer = (unsigned char*) malloc(BUFFER_SIZE*size); - if (send_buffer == NULL || recv_buffer == NULL) { - fprintf(stderr, "Memory allocation failed!\n"); - MPI_Abort(MPI_COMM_WORLD, 1); - return -1; - } - - srand(time(NULL)*rank); - for (int i = 0; i < BUFFER_SIZE*size; i++) { - send_buffer[i] = rand()*rank % size; - } - - double burst_pause; - double burst_length; - if(argc >= 2){ - burst_pause = atof(argv[1]); - }else{ - std::cerr << "Not enough arguments. Usage: ./bursty_noise_a2a " << std::endl; - return 1; - } - if(argc >= 3){ - burst_length = atof(argv[2]); - } else { - std::cerr << "Not enough arguments. Usage: ./bursty_noise_a2a " << std::endl; - return 1; - } - - - bool burst_pause_rand = false; - double burst_start_time; - double measure_start_time; - double burst_length_mean=burst_length; - double burst_pause_mean=burst_pause; - int burst_cont=0; - - while (1) { - burst_start_time=MPI_Wtime(); - do { - all2all_memcpy(send_buffer, BUFFER_SIZE, MPI_BYTE, recv_buffer, BUFFER_SIZE, MPI_BYTE, MPI_COMM_WORLD); - custom_alltoall(send_buffer, BUFFER_SIZE, MPI_BYTE, recv_buffer, BUFFER_SIZE, MPI_BYTE, MPI_COMM_WORLD); - - if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ - if(rank == 0){ /*master decides if burst should be continued*/ - burst_cont=((MPI_Wtime()-burst_start_time) -#include -#include -#include -#include -#include -#include -#include -#include "common.h" - -int main(int argc, char** argv) { - MPI_Init(&argc, &argv); - - int size, rank; - MPI_Comm_size(MPI_COMM_WORLD, &size); - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - - const size_t BUFFER_SIZE = 2 * 1024 * 1024; // bytes per peer 2MiB - - // Each process will send a chunk to every other process - unsigned char *buffer = (unsigned char*) malloc_align(BUFFER_SIZE); - if (buffer == NULL) { - fprintf(stderr, "Memory allocation failed!\n"); - MPI_Abort(MPI_COMM_WORLD, 1); - return -1; - } - - srand(time(NULL)*rank); - for (int i = 0; i < BUFFER_SIZE; i++) { - buffer[i] = rand()*rank % size; - } - - double burst_pause; - double burst_length; - if(argc >= 2){ - burst_pause = atof(argv[1]); - }else{ - std::cerr << "Not enough arguments. Usage: ./bursty_noise_a2a " << std::endl; - return 1; - } - if(argc >= 3){ - burst_length = atof(argv[2]); - } else { - std::cerr << "Not enough arguments. Usage: ./bursty_noise_a2a " << std::endl; - return 1; - } - - - bool burst_pause_rand = false; - double burst_start_time; - double measure_start_time; - double burst_length_mean=burst_length; - double burst_pause_mean=burst_pause; - int burst_cont=0; - - while (1) { - std::vector requests; - burst_start_time=MPI_Wtime(); - do { - // INCAST START - requests.clear(); - - if (rank == 0) { - - for (int sender = 1; sender < size; ++sender) { - MPI_Request req; - MPI_Irecv(buffer, BUFFER_SIZE, MPI_BYTE, sender, 0, MPI_COMM_WORLD, &req); - requests.push_back(req); - } - - } else { - MPI_Request req; - MPI_Isend(buffer, BUFFER_SIZE, MPI_BYTE, 0, 0, MPI_COMM_WORLD, &req); - requests.push_back(req); - } - - MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE); - // INCAST END - - if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ - if(rank == 0){ /*master decides if burst should be continued*/ - burst_cont=((MPI_Wtime()-burst_start_time) bin/alltoall_nb + +file(GLOB_RECURSE c_srcs CONFIGURE_DEPENDS "*.c") +file(GLOB_RECURSE cpp_srcs CONFIGURE_DEPENDS "*.cpp") + +foreach(src IN LISTS c_srcs cpp_srcs) + get_filename_component(name ${src} NAME_WE) + get_filename_component(ext ${src} EXT) + + add_executable(${name} ${src}) + target_include_directories(${name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + + if(ext STREQUAL ".c") + target_link_libraries(${name} PRIVATE MPI::MPI_C m) + else() + target_link_libraries(${name} PRIVATE MPI::MPI_CXX m) + endif() +endforeach() diff --git a/src/allgather/allgather_b.c b/src/allgather/allgather_b.c new file mode 100644 index 000000000..7d49a42fe --- /dev/null +++ b/src/allgather/allgather_b.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +int main(int argc, char** argv){ + + /*init MPI world*/ + MPI_Init(&argc,&argv); + MPI_Comm_size(MPI_COMM_WORLD, &w_size); + MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); + + /*register signal handler*/ + signal(SIGUSR1,sig_handler); + + /*default values*/ + int master_rank=0; + bool master_rand=false; + + int rand_seed=1; + + int msg_size=1024; + int measure_granularity=1; + max_samples=1000; + + warm_up_iters=5; + int max_iters=1; + bool endless=false; + + double burst_length=0.0; + bool burst_length_rand=false; + double burst_pause=0.0; + bool burst_pause_rand=false; + + int i,k; + + /*read cmd line args*/ + for(i=1;i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +int main(int argc, char** argv){ + + /*init MPI world*/ + MPI_Init(&argc,&argv); + MPI_Comm_size(MPI_COMM_WORLD, &w_size); + MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); + + /*register signal handler*/ + signal(SIGUSR1,sig_handler); + + /*default values*/ + int master_rank=0; + bool master_rand=false; + + int rand_seed=1; + + int msg_size=1024; + int measure_granularity=1; + max_samples=1000; + + warm_up_iters=5; + int max_iters=1; + bool endless=false; + + double burst_length=0.0; + bool burst_length_rand=false; + double burst_pause=0.0; + bool burst_pause_rand=false; + + int i,k; + + /*read cmd line args*/ + for(i=1;i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +int main(int argc, char** argv){ + + /*init MPI world*/ + MPI_Init(&argc,&argv); + MPI_Comm_size(MPI_COMM_WORLD, &w_size); + MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); + + /*register signal handler*/ + signal(SIGUSR1,sig_handler); + + /*default values*/ + int master_rank=0; + bool master_rand=false; + + int rand_seed=1; + + int measure_granularity=1; + max_samples=1000; + + warm_up_iters=5; + int max_iters=1; + bool endless=false; + + double burst_length=0.0; + bool burst_length_rand=false; + double burst_pause=0.0; + bool burst_pause_rand=false; + + int i,k; + + /*read cmd line args*/ + for(i=1;i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +int main(int argc, char** argv){ + + /*init MPI world*/ + MPI_Init(&argc,&argv); + MPI_Comm_size(MPI_COMM_WORLD, &w_size); + MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); + + /*register signal handler*/ + signal(SIGUSR1,sig_handler); + + /*default values*/ + int master_rank=0; + bool master_rand=false; + + int rand_seed=1; + + int msg_size=1024; + int measure_granularity=1; + max_samples=1000; + + warm_up_iters=5; + int max_iters=1; + bool endless=false; + + double burst_length=0.0; + bool burst_length_rand=false; + double burst_pause=0.0; + bool burst_pause_rand=false; + + int i,k; + + /*read cmd line args*/ + for(i=1;i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +int main(int argc, char** argv){ + + /*init MPI world*/ + MPI_Init(&argc,&argv); + MPI_Comm_size(MPI_COMM_WORLD, &w_size); + MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); + + /*register signal handler*/ + signal(SIGUSR1,sig_handler); + + /*default values*/ + int master_rank=0; + bool master_rand=false; + + int rand_seed=1; + + int msg_size=1024; + int measure_granularity=1; + max_samples=1000; + + warm_up_iters=5; + int max_iters=1; + bool endless=false; + + double burst_length=0.0; + bool burst_length_rand=false; + double burst_pause=0.0; + bool burst_pause_rand=false; + + int i,k; + + /*read cmd line args*/ + for(i=1;i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +int main(int argc, char** argv){ + + /*init MPI world*/ + MPI_Init(&argc,&argv); + MPI_Comm_size(MPI_COMM_WORLD, &w_size); + MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); + + /*register signal handler*/ + signal(SIGUSR1,sig_handler); + + /*default values*/ + int master_rank=0; + bool master_rand=false; + + int rand_seed=1; + + int msg_size=1024; + int measure_granularity=1; + max_samples=1000; + + warm_up_iters=5; + int max_iters=1; + bool endless=false; + + double burst_length=0.0; + bool burst_length_rand=false; + double burst_pause=0.0; + bool burst_pause_rand=false; + + int npartners=1; /*number of random communication partners per rank (-k flag)*/ + + int i,k,p; + + /*read cmd line args*/ + for(i=1;i= 1\n",npartners); + exit(-1); + } + } + if(npartners>w_size-1){ + if(my_rank==master_rank){ + fprintf(stderr,"Warning: k (%d) capped to w_size-1 (%d)\n",npartners,w_size-1); + } + npartners=w_size-1; + } + + /* + * Build npartners random permutations of [0, w_size-1]. + * For permutation p, rank i sends to perms[p*w_size + i]. + * Each rank appears as a target exactly once per permutation, + * so every rank sends npartners messages and receives npartners messages. + * Partners are fixed for the entire run (generated from rand_seed). + */ + int *perms; + perms=(int*)malloc_align(sizeof(int)*npartners*w_size); + if(perms==NULL){ + fprintf(stderr,"Failed to allocate permutation table on rank %d\n",my_rank); + exit(-1); + } + for(p=0;p +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +int main(int argc, char** argv){ + + /*init MPI world*/ + MPI_Init(&argc,&argv); + MPI_Comm_size(MPI_COMM_WORLD, &w_size); + MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); + + /*register signal handler*/ + signal(SIGUSR1,sig_handler); + + /*default values*/ + int master_rank=0; + bool master_rand=false; + + int rand_seed=1; + + int msg_size=1024; + int measure_granularity=1; + max_samples=1000; + + warm_up_iters=5; + int max_iters=1; + bool endless=false; + + double burst_length=0.0; + bool burst_length_rand=false; + double burst_pause=0.0; + bool burst_pause_rand=false; + + char *comm_mode="offpair"; + int target_offset=1; + + int i,k; + + /*read cmd line args*/ + for(i=1;i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +int main(int argc, char** argv){ + + /*init MPI world*/ + MPI_Init(&argc,&argv); + MPI_Comm_size(MPI_COMM_WORLD, &w_size); + MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); + + /*register signal handler*/ + signal(SIGUSR1,sig_handler); + + /*default values*/ + int master_rank=0; + bool master_rand=false; + + int rand_seed=1; + + int msg_size=1024; + int measure_granularity=1; + max_samples=1000; + + warm_up_iters=5; + int max_iters=1; + bool endless=false; + + double burst_length=0.0; + bool burst_length_rand=false; + double burst_pause=0.0; + bool burst_pause_rand=false; + + int i,k; + + /*read cmd line args*/ + for(i=1;i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +int main(int argc, char** argv){ + + /*init MPI world*/ + MPI_Init(&argc,&argv); + MPI_Comm_size(MPI_COMM_WORLD, &w_size); + MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); + + /*register signal handler*/ + signal(SIGUSR1,sig_handler); + + /*default values*/ + int master_rank=0; + bool master_rand=false; + + int rand_seed=1; + + int msg_size=1024; + int measure_granularity=1; + max_samples=1000; + + warm_up_iters=5; + int max_iters=1; + bool endless=false; + + double burst_length=0.0; + bool burst_length_rand=false; + double burst_pause=0.0; + bool burst_pause_rand=false; + + int i,k; + + /*read cmd line args*/ + for(i=1;i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +int main(int argc, char** argv){ + + /*init MPI world*/ + MPI_Init(&argc,&argv); + MPI_Comm_size(MPI_COMM_WORLD, &w_size); + MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); + + /*register signal handler*/ + signal(SIGUSR1,sig_handler); + + /*default values*/ + int master_rank=0; + bool master_rand=false; + + int rand_seed=1; + + int msg_size=1024; /*recv size per rank; total send size = w_size * msg_size*/ + int measure_granularity=1; + max_samples=1000; + + warm_up_iters=5; + int max_iters=1; + bool endless=false; + + double burst_length=0.0; + bool burst_length_rand=false; + double burst_pause=0.0; + bool burst_pause_rand=false; + + int i,k; + + /*read cmd line args*/ + for(i=1;i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +int main(int argc, char** argv){ + + /*init MPI world*/ + MPI_Init(&argc,&argv); + MPI_Comm_size(MPI_COMM_WORLD, &w_size); + MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); + + /*register signal handler*/ + signal(SIGUSR1,sig_handler); + + /*default values*/ + int master_rank=0; + bool master_rand=false; + + int rand_seed=1; + + int msg_size=1024; /*recv size per rank; total send size = w_size * msg_size*/ + int measure_granularity=1; + max_samples=1000; + + warm_up_iters=5; + int max_iters=1; + bool endless=false; + + double burst_length=0.0; + bool burst_length_rand=false; + double burst_pause=0.0; + bool burst_pause_rand=false; + + int i,k; + + /*read cmd line args*/ + for(i=1;i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +int main(int argc, char** argv){ + + /*init MPI world*/ + MPI_Init(&argc,&argv); + MPI_Comm_size(MPI_COMM_WORLD, &w_size); + MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); + + /*register signal handler*/ + signal(SIGUSR1,sig_handler); + + /*default values*/ + int master_rank=0; + bool master_rand=false; + + int rand_seed=1; + + int msg_size=1024; + int measure_granularity=1; + max_samples=1000; + + warm_up_iters=5; + int max_iters=1; + bool endless=false; + + double burst_length=0.0; + bool burst_length_rand=false; + double burst_pause=0.0; + bool burst_pause_rand=false; + + int i,k; + + /*read cmd line args*/ + for(i=1;i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +int main(int argc, char** argv){ + + /*init MPI world*/ + MPI_Init(&argc,&argv); + MPI_Comm_size(MPI_COMM_WORLD, &w_size); + MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); + + /*register signal handler*/ + signal(SIGUSR1,sig_handler); + + /*default values*/ + int master_rank=0; + bool master_rand=false; + + int rand_seed=1; + + int msg_size=1024; + int measure_granularity=1; + max_samples=1000; + + warm_up_iters=5; + int max_iters=1; + bool endless=false; + + double burst_length=0.0; + bool burst_length_rand=false; + double burst_pause=0.0; + bool burst_pause_rand=false; + + int i,k; + + /*read cmd line args*/ + for(i=1;i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +int main(int argc, char** argv){ + + /*init MPI world*/ + MPI_Init(&argc,&argv); + MPI_Comm_size(MPI_COMM_WORLD, &w_size); + MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); + + /*register signal handler*/ + signal(SIGUSR1,sig_handler); + + /*default values*/ + int master_rank=0; + bool master_rand=false; + + int rand_seed=1; + + int msg_size=1024; + int measure_granularity=1; + max_samples=1000; + + warm_up_iters=5; + int max_iters=1; + bool endless=false; + + double burst_length=0.0; + bool burst_length_rand=false; + double burst_pause=0.0; + bool burst_pause_rand=false; + + int dimx=0; /*0 means auto-factor via MPI_Dims_create*/ + bool periodic=false; + + int i,k; + + /*read cmd line args*/ + for(i=1;i0){ + if(w_size%dimx!=0){ + if(my_rank==master_rank){ + fprintf(stderr,"dimx (%d) does not divide w_size (%d)\n",dimx,w_size); + exit(-1); + } + } + dims[0]=dimx; + dims[1]=w_size/dimx; + }else{ + MPI_Dims_create(w_size,2,dims); + } + + if(periodic){ + periods[0]=1; + periods[1]=1; + } + + MPI_Comm cart_comm; + MPI_Cart_create(MPI_COMM_WORLD,2,dims,periods,0,&cart_comm); + + /*get 4-neighbor ranks (MPI_PROC_NULL for missing boundary neighbors)*/ + /*direction 0 = rows: north = row-1, south = row+1*/ + /*direction 1 = cols: west = col-1, east = col+1*/ + int north, south, west, east; + MPI_Cart_shift(cart_comm,0,1,&north,&south); + MPI_Cart_shift(cart_comm,1,1,&west,&east); + + int neighbors[4]={north,south,west,east}; + + /*pin to core*/ + /*cpu_set_t mask; + CPU_ZERO(&mask); + CPU_SET(1, &mask); + sched_setaffinity(0, sizeof(mask), &mask);*/ + + /*allocate buffers*/ + /*4 directions, each of size msg_size; granularity batches them*/ + int send_buf_size, recv_buf_size; + unsigned char *send_buf; + unsigned char *recv_buf; + MPI_Request *send_requests; + MPI_Request *recv_requests; + + send_buf_size=msg_size; + recv_buf_size=4*measure_granularity*msg_size; + + send_buf=(unsigned char*)malloc_align(send_buf_size); + recv_buf=(unsigned char*)malloc_align(recv_buf_size); + durations=(double *)malloc_align(sizeof(double)*max_samples); + send_requests=(MPI_Request*)malloc_align(sizeof(MPI_Request)*4*measure_granularity); + recv_requests=(MPI_Request*)malloc_align(sizeof(MPI_Request)*4*measure_granularity); + + if(send_buf==NULL || recv_buf==NULL || durations==NULL || send_requests==NULL || recv_requests==NULL){ + fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); + exit(-1); + } + + /*fill send buffer with dummies*/ + for(i=0;i + +int main(int argc, char** argv){ + MPI_Init(&argc,&argv); + MPI_Finalize(); +} From cfce9ff353a84e0bf5d88dba2dd1b08cfe8e25c7 Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Tue, 26 May 2026 18:30:05 +0200 Subject: [PATCH 02/26] [IMP] Refactoring, pretty print and README. --- README.md | 263 +++++++++++++++++++ blink.png | Bin 28907 -> 0 bytes blink.svg | 334 ------------------------- src/allgather/allgather_b.c | 77 +----- src/allgather/allgather_comm_only.cpp | 85 +------ src/allgather/allgather_nb.c | 77 +----- src/allreduce/allreduce_b.c | 79 +----- src/allreduce/allreduce_nb.c | 79 +----- src/alltoall/alltoall_b.c | 79 +----- src/alltoall/alltoall_comm_only.cpp | 83 +----- src/alltoall/alltoall_man.c | 79 +----- src/alltoall/alltoall_nb.c | 79 +----- src/barrier/barrier_b.c | 75 +----- src/barrier/barrier_nb.c | 73 +----- src/broadcast/broadcast_b.c | 79 +----- src/broadcast/broadcast_nb.c | 79 +----- src/common.h | 186 ++++++++++---- src/gather/gather_b.c | 77 +----- src/gather/gather_nb.c | 77 +----- src/incast/incast_b.c | 79 +----- src/incast/incast_bsnbr.c | 79 +----- src/incast/incast_get.c | 79 +----- src/incast/incast_nb.c | 79 +----- src/incast/incast_put.c | 79 +----- src/kpartners/kpartners_nb.c | 78 +----- src/pairwise/pairwise_b.c | 83 +----- src/pairwise/pairwise_bsnbr.c | 85 +------ src/pairwise/pairwise_nb.c | 85 +------ src/pingpong/pingpong_b.c | 79 +----- src/pingpong/pingpong_pairwise_b.c | 80 +----- src/reduce/reduce_b.c | 77 +----- src/reduce/reduce_nb.c | 77 +----- src/reduce_scatter/reduce_scatter_b.c | 77 +----- src/reduce_scatter/reduce_scatter_nb.c | 77 +----- src/ring/ring_bsnbr.c | 79 +----- src/ring/ring_nb.c | 79 +----- src/scatter/scatter_b.c | 77 +----- src/scatter/scatter_nb.c | 77 +----- src/stencil/stencil_2d_nb.c | 82 +----- src/tools/checker.c | 79 +----- 40 files changed, 693 insertions(+), 2933 deletions(-) create mode 100644 README.md delete mode 100644 blink.png delete mode 100644 blink.svg diff --git a/README.md b/README.md new file mode 100644 index 000000000..a5943d0b6 --- /dev/null +++ b/README.md @@ -0,0 +1,263 @@ +
+ Blink logo +

Blink

+

MPI micro-benchmarks for collective and point-to-point communication patterns

+
+ +--- + +Blink is a collection of MPI benchmarks designed for long-running, in-situ measurement of network behaviour under realistic traffic conditions. Each benchmark runs for a configurable number of iterations (or endlessly until interrupted), records per-iteration latency on every rank, and emits a CSV summary when it finishes — either naturally or via `SIGUSR1`. + +## How Blink differs from other benchmarking suites + +Most MPI benchmark suites run a fixed iteration sweep and report a single aggregate table (min / median / max per message size) before exiting. Blink takes a different approach on three axes: + +**Temporal evolution.** Blink records one measurement per iteration rather than collapsing everything into a final summary. This lets you observe how latency evolves over time, detect jitter events, and correlate performance spikes with external activity on the system. + +**Burst traffic modeling.** Real applications rarely issue communication at a steady, uniform rate. Blink supports configurable burst-pause cycles, including exponentially distributed burst lengths and pause durations, so the generated traffic pattern can more closely reflect the bursty nature of production workloads. + +**Long-running operation.** Blink is designed to run alongside real workloads or as a background monitor. It can run endlessly (`-endl`), keeps only the most recent *N* samples in a ring buffer (`-maxsamples`), and responds to `SIGUSR1` with a clean shutdown — collecting and printing whatever has been measured so far before calling `MPI_Finalize`. + +## Building + +Requirements: CMake ≥ 3.16, an MPI implementation (e.g. OpenMPI, MPICH, Intel MPI). + +```bash +cmake -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build -j$(nproc) +``` + +Binaries are placed in `build/bin/`. Adding a new `.c` or `.cpp` file anywhere under `src/` is enough — CMake picks it up automatically on the next configure. + +## Common flags + +Every benchmark understands the following flags: + +| Flag | Default | Description | +|------|---------|-------------| +| `-msgsize ` | `1024` | Message size in bytes | +| `-iter ` | `1` | Number of measured iterations (after warm-up) | +| `-warmup ` | `5` | Number of warm-up iterations (not recorded) | +| `-endl` | off | Run endlessly until `SIGUSR1` | +| `-grty ` | `1` | Measurement granularity — number of MPI calls batched per timed window | +| `-maxsamples ` | `1000` | Maximum recorded samples (ring buffer) | +| `-mrank ` | `0` | Rank that collects and prints results | +| `-mrand` | off | Pick master rank randomly | +| `-seed ` | `1` | RNG seed (shared across ranks) | +| `-blength ` | `0` | Burst length in seconds (0 = single shot per iteration) | +| `-blrand` | off | Randomise burst length (exponential distribution, mean = `-blength`) | +| `-bpause ` | `0` | Pause between bursts in seconds | +| `-bprand` | off | Randomise pause length (exponential distribution, mean = `-bpause`) | +| `-pretty-print` | off | Human-readable table output instead of CSV (see [Output format](#output-format)) | + +## Benchmarks + +Benchmarks are grouped by traffic pattern. The suffix convention is: + +| Suffix | Meaning | +|--------|---------| +| `_b` | Blocking MPI call | +| `_nb` | Non-blocking MPI call (`MPI_I*` + `MPI_Waitall`) | +| `_bsnbr` | Non-blocking send, blocking receive (anti-deadlock variant) | +| `_man` | Manual / hand-rolled implementation (no collective primitive) | +| `_comm_only` | Communication-only variant (no computation, C++) | +| `_get` / `_put` | One-sided (RMA) variant | + +### Collectives + +#### All-to-all — `alltoall/` +Every rank sends a distinct message to every other rank. + +| Binary | MPI primitive | +|--------|--------------| +| `alltoall_b` | `MPI_Alltoall` | +| `alltoall_nb` | `MPI_Ialltoall` | +| `alltoall_man` | `MPI_Isend` / `MPI_Irecv` (manual) | +| `alltoall_comm_only` | `MPI_Isend` / `MPI_Irecv` (C++, no memory init) | + +#### All-gather — `allgather/` +Every rank broadcasts its buffer; all ranks collect the full result. + +| Binary | MPI primitive | +|--------|--------------| +| `allgather_b` | `MPI_Allgather` | +| `allgather_nb` | `MPI_Iallgather` | +| `allgather_comm_only` | `MPI_Isend` / `MPI_Irecv` (C++) | + +#### All-reduce — `allreduce/` +Global reduction (`MPI_SUM` over `double`) delivered to all ranks. + +| Binary | MPI primitive | +|--------|--------------| +| `allreduce_b` | `MPI_Allreduce` | +| `allreduce_nb` | `MPI_Iallreduce` | + +#### Barrier — `barrier/` + +| Binary | MPI primitive | +|--------|--------------| +| `barrier_b` | `MPI_Barrier` | +| `barrier_nb` | `MPI_Ibarrier` | + +#### Broadcast — `broadcast/` +Root sends one buffer to all other ranks. + +| Binary | MPI primitive | +|--------|--------------| +| `broadcast_b` | `MPI_Bcast` | +| `broadcast_nb` | `MPI_Ibcast` | + +#### Gather — `gather/` +All ranks send to root; root collects all messages. + +| Binary | MPI primitive | +|--------|--------------| +| `gather_b` | `MPI_Gather` | +| `gather_nb` | `MPI_Igather` | + +#### Scatter — `scatter/` +Root distributes a distinct chunk to every rank. + +| Binary | MPI primitive | +|--------|--------------| +| `scatter_b` | `MPI_Scatter` | +| `scatter_nb` | `MPI_Iscatter` | + +#### Reduce — `reduce/` +Global reduction (`MPI_SUM` over `double`) to root only. + +| Binary | MPI primitive | +|--------|--------------| +| `reduce_b` | `MPI_Reduce` | +| `reduce_nb` | `MPI_Ireduce` | + +#### Reduce-scatter — `reduce_scatter/` +Reduction followed by a scatter of the result chunks. + +| Binary | MPI primitive | +|--------|--------------| +| `reduce_scatter_b` | `MPI_Reduce_scatter` | +| `reduce_scatter_nb` | `MPI_Ireduce_scatter` | + +--- + +### Point-to-point patterns + +#### Ping-pong — `pingpong/` +Latency measurement between a pair of ranks. + +| Binary | Description | +|--------|------------| +| `pingpong_b` | Single pair (rank 0 ↔ rank 1) | +| `pingpong_pairwise_b` | All pairs simultaneously | + +#### Incast — `incast/` +All non-root ranks send to root simultaneously. + +| Binary | Variant | +|--------|---------| +| `incast_b` | Blocking receive at root | +| `incast_nb` | Non-blocking (`MPI_Irecv` at root) | +| `incast_bsnbr` | Non-blocking send, blocking receive | +| `incast_get` | One-sided: root issues `MPI_Get` from each sender | +| `incast_put` | One-sided: senders issue `MPI_Put` to root window | + +#### Pairwise — `pairwise/` +Each rank exchanges messages with exactly one partner. Partners are chosen by offset or at random. + +| Binary | Variant | +|--------|---------| +| `pairwise_b` | Blocking (`MPI_Sendrecv`) | +| `pairwise_nb` | Non-blocking (`MPI_Isend` / `MPI_Irecv`) | +| `pairwise_bsnbr` | Non-blocking send, blocking receive | + +Extra flags: `-offset ` (default `1`) — fixed partner offset; `-mode ` (default `offpair`) — pairing mode (`offpair` for fixed offset, `rand` for random pairing). + +#### Ring — `ring/` +Each rank sends to its right neighbour and receives from its left. + +| Binary | Variant | +|--------|---------| +| `ring_nb` | Non-blocking (`MPI_Isend` / `MPI_Irecv`) | +| `ring_bsnbr` | Non-blocking send, blocking receive | + +Extra flag: `-rring` — randomise the ring order instead of using rank order. + +--- + +### Synthetic / application-inspired patterns + +#### 2-D stencil — `stencil/` +Each rank exchanges halo data with its four Cartesian neighbours (north, south, west, east). Uses an `MPI_Cart_create` communicator. Boundary ranks have `MPI_PROC_NULL` neighbours which complete immediately with no data transfer, so no special-casing is needed for edge ranks. + +Binary: `stencil_2d_nb` + +Extra flags: + +| Flag | Default | Description | +|------|---------|-------------| +| `-dimx ` | `0` | Fix the X dimension of the process grid; Y is derived as `w_size / X`. `0` lets `MPI_Dims_create` choose automatically. | +| `-periodic` | off | Make the Cartesian grid periodic (toroidal) in both dimensions | + +```bash +mpirun -n 16 build/bin/stencil_2d_nb -msgsize 65536 -iter 100 -dimx 4 +``` + +#### k-random partners — `kpartners/` +Each rank communicates with *k* randomly chosen partners per iteration. The partner sets are derived from *k* independent random permutations (same seed on all ranks), guaranteeing that the resulting graph is k-regular: every rank sends exactly *k* messages and receives exactly *k* messages. + +Binary: `kpartners_nb` + +Extra flag: + +| Flag | Default | Description | +|------|---------|-------------| +| `-k ` | `1` | Number of random partners per rank (capped at `w_size - 1`) | + +```bash +mpirun -n 32 build/bin/kpartners_nb -k 4 -msgsize 4096 -iter 200 +``` + +--- + +## Output format + +### CSV (default) + +All benchmarks print a CSV block to stdout on the master rank. Each line corresponds to one measured iteration: + +``` +Average,Minimum,Maximum,Median,MainRank +,,,, +... +Ran N iterations. Measured M iterations. +``` + +Times are in seconds (9 decimal places). The `Average`, `Minimum`, `Maximum`, and `Median` columns are computed across all MPI ranks for that iteration. `MainRank` is the raw value recorded on the master rank. + +### Pretty-print + +Pass `-pretty-print` to get a human-readable table with auto-scaled units (`us` / `ms` / `s`) instead of CSV: + +```bash +mpirun -n 8 build/bin/alltoall_nb -iter 5 -pretty-print +``` + +``` + # avg min max median + ------------------------------------------------------------- + 1 12.45 us 9.12 us 18.67 us 11.34 us + 2 11.98 us 9.08 us 17.23 us 11.12 us + ------------------------------------------------------------- + 5 samples · 10 iterations total +``` + +The `MainRank` column is omitted in pretty mode. Pipe through a tool such as `sed 's/\x1b\[[0-9;]*m//g'` to strip ANSI colour codes if needed. + +## Early termination + +Send `SIGUSR1` to the master process to trigger a clean shutdown: remaining iterations are skipped, results collected so far are printed, and all ranks call `MPI_Finalize`. + +```bash +kill -USR1 +``` diff --git a/blink.png b/blink.png deleted file mode 100644 index 0a828151ca7182c94a89e69b1deabd6444053d33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28907 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - BLINK - - - diff --git a/src/allgather/allgather_b.c b/src/allgather/allgather_b.c index 7d49a42fe..0b82a8a5a 100644 --- a/src/allgather/allgather_b.c +++ b/src/allgather/allgather_b.c @@ -20,78 +20,15 @@ int main(int argc, char** argv){ /*register signal handler*/ signal(SIGUSR1,sig_handler); - /*default values*/ - int master_rank=0; - bool master_rand=false; - - int rand_seed=1; - - int msg_size=1024; - int measure_granularity=1; - max_samples=1000; - - warm_up_iters=5; - int max_iters=1; - bool endless=false; - - double burst_length=0.0; - bool burst_length_rand=false; - double burst_pause=0.0; - bool burst_pause_rand=false; - - int i,k; - - /*read cmd line args*/ - for(i=1;i max_samples) // Wrapped the sampling recording + if (curr_iters > max_samples) /* wrapped the sampling ring buffer */ { num_samples = max_samples; - start_index = curr_iters % max_samples; + start_index = curr_iters % max_samples; tmp_buf = (double *)malloc(sizeof(double)*num_samples); - // Copy the data from the durations buffer to tmp_buf (so that it is in the proper order) + /* copy in chronological order */ memcpy(tmp_buf, &(durations[start_index]), sizeof(double)*(num_samples - start_index)); memcpy(&tmp_buf[num_samples - start_index], durations, sizeof(double)*start_index); } @@ -71,51 +134,78 @@ static void write_results() tmp_buf = (double *)malloc(sizeof(double)*num_samples); memcpy(tmp_buf, &(durations[start_index]), sizeof(double)*num_samples); } - - double *all_data = (double *)malloc(sizeof(double)*num_samples*w_size); - double *sorting_buf = (double*)malloc(sizeof(double)*w_size); - - if(all_data == NULL || sorting_buf == NULL){ - fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); + double *all_data = (double *)malloc(sizeof(double)*num_samples*w_size); + double *sorting_buf = (double *)malloc(sizeof(double)*w_size); + + if (all_data == NULL || sorting_buf == NULL) { + fprintf(stderr, "Failed to allocate a buffer on rank %d\n", my_rank); exit(-1); } - /*print file header*/ - if (my_rank == master_rank) - { - printf("Average,Minimum,Maximum,Median,MainRank\n"); + /* print header before the gather so it appears first */ + if (my_rank == master_rank) { + if (pretty_output) { + printf("\033[1m\033[36m" + " %5s %12s %12s %12s %12s" + "\033[0m\n", + "#", "avg", "min", "max", "median"); + printf("\033[2m" + " -------------------------------------------------------------" + "\033[0m\n"); + } else { + printf("Average,Minimum,Maximum,Median,MainRank\n"); + } } - // We need to first gather all the data - MPI_Gather(tmp_buf, num_samples, MPI_DOUBLE, all_data, num_samples, MPI_DOUBLE, master_rank, MPI_COMM_WORLD); + MPI_Gather(tmp_buf, num_samples, MPI_DOUBLE, + all_data, num_samples, MPI_DOUBLE, + master_rank, MPI_COMM_WORLD); - if(my_rank == master_rank){ - for(i = 0; i < num_samples; i++){ + if (my_rank == master_rank) { + for (i = 0; i < num_samples; i++) { int j; duration_sum = 0; - for(j = 0; j < w_size; j++){ + for (j = 0; j < w_size; j++) { sorting_buf[j] = all_data[j*num_samples + i]; - duration_sum += sorting_buf[j]; + duration_sum += sorting_buf[j]; } qsort(sorting_buf, w_size, sizeof(double), compare_doubles); - if (w_size % 2 == 0) - { /*even: then median as mean of middle values*/ - duration_median = (sorting_buf[(w_size - 1) / 2] + sorting_buf[w_size / 2]) / 2; - } - else - { /*odd: else median as middle value*/ - duration_median = sorting_buf[(w_size - 1) / 2]; + if (w_size % 2 == 0) /* even: median as mean of two middle values */ + duration_median = (sorting_buf[(w_size-1)/2] + sorting_buf[w_size/2]) / 2.0; + else /* odd: median is the middle value */ + duration_median = sorting_buf[(w_size-1)/2]; + + if (pretty_output) { + char avg_s[24], min_s[24], max_s[24], med_s[24]; + format_duration(avg_s, sizeof(avg_s), duration_sum / w_size); + format_duration(min_s, sizeof(min_s), sorting_buf[0]); + format_duration(max_s, sizeof(max_s), sorting_buf[w_size-1]); + format_duration(med_s, sizeof(med_s), duration_median); + printf(" %5d %12s %12s %12s %12s\n", + i+1, avg_s, min_s, max_s, med_s); + } else { + printf("%.9f,%.9f,%.9f,%.9f,%.9f\n", + duration_sum / w_size, + sorting_buf[0], sorting_buf[w_size-1], + duration_median, tmp_buf[i]); } - printf("%.9f,%.9f,%.9f,%.9f,%.9f\n", duration_sum / w_size, sorting_buf[0], sorting_buf[w_size - 1], duration_median, tmp_buf[i]); } - printf("Ran %d iterations. Measured %d iterations.\n", curr_iters, num_samples); + if (pretty_output) { + printf("\033[2m" + " -------------------------------------------------------------" + "\033[0m\n"); + printf(" %d samples · %d iterations total\n", num_samples, curr_iters); + } else { + printf("Ran %d iterations. Measured %d iterations.\n", curr_iters, num_samples); + } fflush(stdout); } + free(sorting_buf); - free(all_data); + free(all_data); free(tmp_buf); } @@ -127,12 +217,12 @@ void sig_handler(int sig) exit(0); } +/* ── combinatorics helpers ───────────────────────────────────────────────── */ + /*use Fisher-Yates to permute array*/ static void permute(int *a, int n) { - int j; - int t; - int i; + int j, t, i; for (i = n; i > 1; i--) { j = rand() % i; @@ -142,7 +232,7 @@ static void permute(int *a, int n) } } -/*mathematical mod without negativ numbers*/ +/*mathematical mod without negative numbers*/ static int mod(int a, int b) { int c = a % b; @@ -156,9 +246,7 @@ static void random_pairs(int *a, int n) { int i, j; for (i = 0; i < n; i++) - { a[i] = -1; - } if (n % 2 == 1) { n--; @@ -192,12 +280,9 @@ static void random_pairs(int *a, int n) /*produce fixed offset pairs*/ static void offset_pairs(int *a, int n, int o) { - int i; + int i, t; for (i = 0; i < n; i++) - { a[i] = -1; - } - int t; for (i = 0; i < n; i++) { t = mod(i + o, n); @@ -218,12 +303,13 @@ static void offset_pairs(int *a, int n, int o) } #define ALIGNMENT (sysconf(_SC_PAGESIZE)) -static void* malloc_align(size_t size){ +static void* malloc_align(size_t size) +{ void *p = NULL; int ret = posix_memalign(&p, ALIGNMENT, size); - if (ret != 0){ + if (ret != 0) { fprintf(stderr, "Failed to allocate memory on rank\n"); exit(-1); } return p; -} \ No newline at end of file +} diff --git a/src/gather/gather_b.c b/src/gather/gather_b.c index b5f35ed6b..c2696e69c 100644 --- a/src/gather/gather_b.c +++ b/src/gather/gather_b.c @@ -20,78 +20,15 @@ int main(int argc, char** argv){ /*register signal handler*/ signal(SIGUSR1,sig_handler); - /*default values*/ - int master_rank=0; - bool master_rand=false; - - int rand_seed=1; - - int msg_size=1024; - int measure_granularity=1; - max_samples=1000; - - warm_up_iters=5; - int max_iters=1; - bool endless=false; - - double burst_length=0.0; - bool burst_length_rand=false; - double burst_pause=0.0; - bool burst_pause_rand=false; - - int i,k; - - /*read cmd line args*/ - for(i=1;i Date: Tue, 26 May 2026 19:58:24 +0200 Subject: [PATCH 03/26] [ADD] More distributions for random bursts. --- assets/blink.svg | 252 ++++++++++++++++++++++++ assets/blink.svg:Zone.Identifier | 4 + src/allgather/allgather_b.c | 4 +- src/allgather/allgather_comm_only.cpp | 4 +- src/allgather/allgather_nb.c | 4 +- src/allreduce/allreduce_b.c | 4 +- src/allreduce/allreduce_nb.c | 4 +- src/alltoall/alltoall_b.c | 4 +- src/alltoall/alltoall_comm_only.cpp | 4 +- src/alltoall/alltoall_man.c | 4 +- src/alltoall/alltoall_nb.c | 4 +- src/barrier/barrier_b.c | 4 +- src/barrier/barrier_nb.c | 4 +- src/broadcast/broadcast_b.c | 4 +- src/broadcast/broadcast_nb.c | 4 +- src/common.h | 72 ++++++- src/gather/gather_b.c | 4 +- src/gather/gather_nb.c | 4 +- src/incast/incast_b.c | 4 +- src/incast/incast_bsnbr.c | 4 +- src/incast/incast_get.c | 4 +- src/incast/incast_nb.c | 4 +- src/incast/incast_put.c | 4 +- src/kpartners/kpartners_nb.c | 4 +- src/pairwise/pairwise_b.c | 4 +- src/pairwise/pairwise_bsnbr.c | 4 +- src/pairwise/pairwise_nb.c | 4 +- src/pingpong/pingpong_b.c | 4 +- src/pingpong/pingpong_pairwise_b.c | 4 +- src/reduce/reduce_b.c | 4 +- src/reduce/reduce_nb.c | 4 +- src/reduce_scatter/reduce_scatter_b.c | 4 +- src/reduce_scatter/reduce_scatter_nb.c | 4 +- src/ring/ring_bsnbr.c | 4 +- src/ring/ring_nb.c | 4 +- src/scatter/scatter_b.c | 4 +- src/scatter/scatter_nb.c | 4 +- src/stencil/stencil_2d_nb.c | 4 +- src/tools/checker.c | 4 +- src/tools/dist_test.c | 254 +++++++++++++++++++++++++ 40 files changed, 646 insertions(+), 80 deletions(-) create mode 100644 assets/blink.svg create mode 100644 assets/blink.svg:Zone.Identifier create mode 100644 src/tools/dist_test.c diff --git a/assets/blink.svg b/assets/blink.svg new file mode 100644 index 000000000..8810028a6 --- /dev/null +++ b/assets/blink.svg @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Blink + A BENCHMARK FOR LARGE-SCALE INTERCONNECTION NETWORKS + diff --git a/assets/blink.svg:Zone.Identifier b/assets/blink.svg:Zone.Identifier new file mode 100644 index 000000000..92384252a --- /dev/null +++ b/assets/blink.svg:Zone.Identifier @@ -0,0 +1,4 @@ +[ZoneTransfer] +ZoneId=3 +ReferrerUrl=https://claude.ai/chat/dbdd6758-5404-48b7-8114-90d42e4c9ae1 +HostUrl=https://claude.ai/api/organizations/85d1dfa7-3ea1-4687-8108-21fcc1c2c2c3/conversations/dbdd6758-5404-48b7-8114-90d42e4c9ae1/wiggle/download-file?path=%2Fmnt%2Fuser-data%2Foutputs%2Fblink-v3-final.svg diff --git a/src/allgather/allgather_b.c b/src/allgather/allgather_b.c index 0b82a8a5a..b7c4fdc19 100644 --- a/src/allgather/allgather_b.c +++ b/src/allgather/allgather_b.c @@ -81,7 +81,7 @@ int main(int argc, char** argv){ do{ for(k=0;k #include -/*draw a exponentially distributed number with expectation=mean*/ -static double rand_expo(double mean) +/* ── random duration samplers ────────────────────────────────────────────── */ + +/* Exponential: shape parameter accepted for API uniformity but not used — + * the distribution is fully determined by its mean. */ +static double rand_exp(double mean, double shape) { - double lambda = 1.0 / mean; + (void)shape; double u = rand() / (RAND_MAX + 1.0); - return -log(1 - u) / lambda; + return -mean * log(1.0 - u); +} + +/* Pareto: shape = tail exponent α. Must be > 1 for a finite mean. + * Parameterised so that E[X] = mean regardless of α. + * Inverse-CDF method: x = x_m * u^(-1/α), u ~ Uniform(0,1). */ +static double rand_pareto(double mean, double shape) +{ + double alpha = shape; + if (alpha <= 1.0) { + fprintf(stderr, "rand_pareto: shape (alpha) must be > 1, got %g\n", alpha); + exit(-1); + } + double x_m = mean * (alpha - 1.0) / alpha; + double u = (rand() + 0.5) / (RAND_MAX + 1.0); /* shift away from 0 */ + return x_m * pow(u, -1.0 / alpha); +} + +/* Log-normal: shape = σ of the underlying normal. + * Parameterised so that E[X] = mean regardless of σ. + * Box-Muller transform. */ +static double rand_lognormal(double mean, double sigma) +{ + double mu = log(mean) - 0.5 * sigma * sigma; + double u1 = (rand() + 0.5) / (RAND_MAX + 1.0); /* shift away from 0 */ + double u2 = (rand() + 0.5) / (RAND_MAX + 1.0); + double z = sqrt(-2.0 * log(u1)) * cos(2.0 * M_PI * u2); + return exp(mu + sigma * z); +} + +/* Dispatch to the selected sampler. */ +static double rand_duration(double mean, const char *dist, double shape) +{ + if (strcmp(dist, "pareto") == 0) return rand_pareto(mean, shape); + if (strcmp(dist, "lognormal") == 0) return rand_lognormal(mean, shape); + return rand_exp(mean, shape); } /*sleep seconds given as double*/ @@ -53,11 +91,19 @@ static int rand_seed = 1; static int max_iters = 1; static int endless = 0; static double burst_length = 0.0; -static int burst_length_rand = 0; +static int burst_length_rand = 0; /* set by -bldist */ static double burst_pause = 0.0; -static int burst_pause_rand = 0; +static int burst_pause_rand = 0; /* set by -bpdist */ static int pretty_output = 0; +/* distribution selection for burst length and pause (-bldist / -bpdist). + * Values: "exp", "pareto", "lognormal". shape: α for Pareto, σ for log-normal + * (ignored for exp). Defaults give exponential with shape unused. */ +static char burst_dist[16] = "exp"; +static double burst_shape = 1.5; +static char pause_dist[16] = "exp"; +static double pause_shape = 1.5; + /* * Parse the standard set of command-line flags shared by every benchmark. * Unrecognised flags are compacted to the front of argv (after argv[0]) and @@ -79,8 +125,14 @@ static int parse_common_args(int argc, char **argv) else if (strcmp(argv[i], "-warmup") == 0) { warm_up_iters = atoi(argv[++i]); } else if (strcmp(argv[i], "-blength") == 0) { burst_length = atof(argv[++i]); } else if (strcmp(argv[i], "-bpause") == 0) { burst_pause = atof(argv[++i]); } - else if (strcmp(argv[i], "-bprand") == 0) { burst_pause_rand = 1; } - else if (strcmp(argv[i], "-blrand") == 0) { burst_length_rand = 1; } + else if (strcmp(argv[i], "-bldist") == 0) { strncpy(burst_dist, argv[++i], 15); + burst_dist[15] = '\0'; + burst_length_rand = 1; } + else if (strcmp(argv[i], "-bpdist") == 0) { strncpy(pause_dist, argv[++i], 15); + pause_dist[15] = '\0'; + burst_pause_rand = 1; } + else if (strcmp(argv[i], "-blshape") == 0) { burst_shape = atof(argv[++i]); } + else if (strcmp(argv[i], "-bpshape") == 0) { pause_shape = atof(argv[++i]); } else if (strcmp(argv[i], "-seed") == 0) { rand_seed = atoi(argv[++i]); } else if (strcmp(argv[i], "-grty") == 0) { measure_granularity = atoi(argv[++i]); } else if (strcmp(argv[i], "-maxsamples") == 0) { max_samples = atoi(argv[++i]); } @@ -96,6 +148,10 @@ static int parse_common_args(int argc, char **argv) return new_argc; } +/* Convenience wrappers used by benchmark measurement loops. */ +static double sample_burst_length(double mean) { return rand_duration(mean, burst_dist, burst_shape); } +static double sample_pause_length(double mean) { return rand_duration(mean, pause_dist, pause_shape); } + /* ── output helpers ──────────────────────────────────────────────────────── */ /*format a duration (seconds) into a fixed 12-char string with auto-scaled units*/ diff --git a/src/gather/gather_b.c b/src/gather/gather_b.c index c2696e69c..ef5c4220a 100644 --- a/src/gather/gather_b.c +++ b/src/gather/gather_b.c @@ -85,7 +85,7 @@ int main(int argc, char** argv){ do{ for(k=0;k +#include +#include +#include +#include +#include "common.h" + +#define N_SAMPLES 100000 +#define HIST_NBINS 8 +#define HIST_BAR_MAX 36 + +/* Bin edges expressed as multiples of mean. The last bin is open-ended (overflow). */ +static const double EDGES[] = { 0.0, 0.5, 1.0, 1.5, 2.0, 3.0, 5.0, 10.0 }; + +/* ── theoretical CDF functions ──────────────────────────────────────────── */ + +static double cdf_exp(double x, double mean, double shape) +{ + (void)shape; + if (x < 0.0) return 0.0; + return 1.0 - exp(-x / mean); +} + +static double cdf_pareto(double x, double mean, double alpha) +{ + double x_m = mean * (alpha - 1.0) / alpha; + if (x < x_m) return 0.0; + return 1.0 - pow(x_m / x, alpha); +} + +static double cdf_lognormal(double x, double mean, double sigma) +{ + if (x <= 0.0) return 0.0; + double mu = log(mean) - 0.5 * sigma * sigma; + double z = (log(x) - mu) / sigma; + return 0.5 * erfc(-z / M_SQRT2); +} + +typedef double (*cdf_fn)(double x, double p1, double p2); + +/* ── KS statistic D_n ───────────────────────────────────────────────────── */ + +static double ks_statistic(double *sorted, int n, cdf_fn cdf, double p1, double p2) +{ + double D = 0.0; + int i; + for (i = 0; i < n; i++) { + double F = cdf(sorted[i], p1, p2); + double d1 = fabs(F - (double)(i + 1) / n); + double d2 = fabs(F - (double)i / n); + double d = d1 > d2 ? d1 : d2; + if (d > D) D = d; + } + return D; +} + +static double ks_critical(int n) +{ + return 1.628 / sqrt((double)n); /* 1 % significance, Kolmogorov approx */ +} + +/* ── ASCII histogram ─────────────────────────────────────────────────────── */ + +static void print_histogram(double *sorted, int n, + cdf_fn cdf, double mean, double p1, double p2) +{ + int counts[HIST_NBINS] = {0}; + int b, i; + + /* bin the (already sorted) samples using linear edges relative to mean */ + b = 0; + for (i = 0; i < n; i++) { + while (b < HIST_NBINS - 1 && sorted[i] >= EDGES[b + 1] * mean) + b++; + counts[b]++; + } + + /* find the tallest bar for scaling */ + int max_count = 1; + for (b = 0; b < HIST_NBINS; b++) + if (counts[b] > max_count) max_count = counts[b]; + + printf("\n"); + for (b = 0; b < HIST_NBINS; b++) { + double lo = EDGES[b] * mean; + double hi = (b < HIST_NBINS - 1) ? EDGES[b + 1] * mean : INFINITY; + double theory = cdf(isinf(hi) ? 1e300 : hi, p1, p2) - cdf(lo, p1, p2); + double emp = (double)counts[b] / n; + int bar_len = (int)round(emp / ((double)max_count / n) * HIST_BAR_MAX); + + /* format the two bin-edge strings (12 chars each, from format_duration) */ + char lo_s[24], hi_s[24]; + format_duration(lo_s, sizeof(lo_s), lo); + if (isinf(hi)) + snprintf(hi_s, sizeof(hi_s), " %8s", "inf"); + else + format_duration(hi_s, sizeof(hi_s), hi); + + /* print range, bar, empirical %, theoretical % */ + printf(" [%s - %s] ", lo_s, hi_s); + for (i = 0; i < bar_len; i++) printf("█"); + for (i = bar_len; i < HIST_BAR_MAX; i++) printf(" "); + printf(" %5.1f%% (theory %5.1f%%)\n", emp * 100.0, theory * 100.0); + } + printf("\n"); +} + +/* ── sampler wrappers (uniform signature) ───────────────────────────────── */ + +static double wrap_exp (double mean, double shape) { return rand_exp(mean, shape); } +static double wrap_pareto (double mean, double shape) { return rand_pareto(mean, shape); } +static double wrap_lognormal(double mean, double shape) { return rand_lognormal(mean, shape);} + +/* ── run one test case ──────────────────────────────────────────────────── */ + +static int run_test(const char *label, + cdf_fn cdf, + double (*sampler)(double, double), + double mean, + double shape, + double theoretical_var) /* NAN = skip variance check */ +{ + double *samples = malloc(sizeof(double) * N_SAMPLES); + int i, all_pass = 1; + + for (i = 0; i < N_SAMPLES; i++) + samples[i] = sampler(mean, shape); + + /* empirical mean */ + double sum = 0.0; + for (i = 0; i < N_SAMPLES; i++) sum += samples[i]; + double emp_mean = sum / N_SAMPLES; + + /* empirical variance */ + double sum2 = 0.0; + for (i = 0; i < N_SAMPLES; i++) { double d = samples[i] - emp_mean; sum2 += d * d; } + double emp_var = sum2 / (N_SAMPLES - 1); + + /* sort once — used by both histogram and KS test */ + qsort(samples, N_SAMPLES, sizeof(double), compare_doubles); + + /* histogram first so it appears right under the case header */ + print_histogram(samples, N_SAMPLES, cdf, mean, mean, shape); + + /* KS test */ + double D = ks_statistic(samples, N_SAMPLES, cdf, mean, shape); + double D_crit = ks_critical(N_SAMPLES); + + double mean_err = fabs(emp_mean - mean) / mean * 100.0; + int mean_ok = mean_err < 1.0; + int ks_ok = D < D_crit; + if (!mean_ok || !ks_ok) all_pass = 0; + + printf(" mean err=%5.2f%% %s | KS D=%.4f crit=%.4f %s\n", + mean_err, mean_ok ? "OK " : "FAIL", D, D_crit, ks_ok ? "OK" : "FAIL"); + + if (!isnan(theoretical_var)) { + double var_err = fabs(emp_var - theoretical_var) / theoretical_var * 100.0; + int var_ok = var_err < 10.0; + if (!var_ok) all_pass = 0; + printf(" variance: empirical=%.4e theoretical=%.4e err=%5.1f%% %s\n", + emp_var, theoretical_var, var_err, var_ok ? "OK" : "FAIL"); + } + + free(samples); + return all_pass; +} + +/* ── main ───────────────────────────────────────────────────────────────── */ + +int main(int argc, char **argv) +{ + MPI_Init(&argc, &argv); + MPI_Comm_size(MPI_COMM_WORLD, &w_size); + MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); + + if (my_rank != 0) { MPI_Finalize(); return 0; } + + srand(42); /* fixed seed for reproducibility */ + + int total = 0, passed = 0; + double mean = 1e-3; /* 1 ms — representative MPI latency */ + + printf("=== Blink distribution sampler verification ===\n"); + printf(" n=%d samples | KS significance=1%% | KS critical=%.4f\n", + N_SAMPLES, ks_critical(N_SAMPLES)); + printf(" histogram bins: [0, 0.5, 1, 1.5, 2, 3, 5, 10, inf] × mean\n"); + printf(" bar height is relative to the tallest bin\n"); + + /* ── exponential ─────────────────────────────────────────────────────── */ + printf("\n--- exp(mean=1ms) [shape unused] ---\n"); + total++; passed += run_test("exp", cdf_exp, wrap_exp, mean, 1.0, mean * mean); + + /* ── Pareto ──────────────────────────────────────────────────────────── */ + printf("\n--- pareto(mean=1ms, alpha=1.5) [infinite variance] ---\n"); + total++; passed += run_test("pareto alpha=1.5", + cdf_pareto, wrap_pareto, mean, 1.5, NAN); + + printf("\n--- pareto(mean=1ms, alpha=2.5) [infinite kurtosis → var check skipped] ---\n"); + total++; passed += run_test("pareto alpha=2.5", + cdf_pareto, wrap_pareto, mean, 2.5, NAN); + + printf("\n--- pareto(mean=1ms, alpha=4.0) [finite kurtosis] ---\n"); + { + double alpha = 4.0; + double x_m = mean * (alpha - 1.0) / alpha; + double var = x_m * x_m * alpha / ((alpha-1.0)*(alpha-1.0)*(alpha-2.0)); + total++; passed += run_test("pareto alpha=4.0", + cdf_pareto, wrap_pareto, mean, alpha, var); + } + + /* ── log-normal ──────────────────────────────────────────────────────── */ + printf("\n--- lognormal(mean=1ms, sigma=0.5) ---\n"); + { + double sigma = 0.5; + double var = (exp(sigma*sigma) - 1.0) * mean * mean; + total++; passed += run_test("lognormal sigma=0.5", + cdf_lognormal, wrap_lognormal, mean, sigma, var); + } + + printf("\n--- lognormal(mean=1ms, sigma=1.0) ---\n"); + { + double sigma = 1.0; + double var = (exp(sigma*sigma) - 1.0) * mean * mean; + total++; passed += run_test("lognormal sigma=1.0", + cdf_lognormal, wrap_lognormal, mean, sigma, var); + } + + printf("\n--- lognormal(mean=1ms, sigma=1.5) [huge kurtosis → var check skipped] ---\n"); + { + double sigma = 1.5; + total++; passed += run_test("lognormal sigma=1.5", + cdf_lognormal, wrap_lognormal, mean, sigma, NAN); + } + + printf("\n%d / %d tests passed.\n", passed, total); + + MPI_Finalize(); + return (passed == total) ? 0 : 1; +} From 883440c306592eafbd52a23d2035382254893cc5 Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Tue, 26 May 2026 19:59:44 +0200 Subject: [PATCH 04/26] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index a5943d0b6..d4f3ab30b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@
- Blink logo -

Blink

-

MPI micro-benchmarks for collective and point-to-point communication patterns

+ Blink logo
--- From 0d0cc349d2acae2f5a6576c3c15529579ff958ac Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Tue, 26 May 2026 20:02:25 +0200 Subject: [PATCH 05/26] [ADD] Logo. --- assets/blink.svg | 414 ++++++++++++++++++++++++----------------------- 1 file changed, 212 insertions(+), 202 deletions(-) diff --git a/assets/blink.svg b/assets/blink.svg index 8810028a6..a0ace4d52 100644 --- a/assets/blink.svg +++ b/assets/blink.svg @@ -1,11 +1,13 @@ + id="rect1" + x="0" + y="0" + style="stroke-width:0.753029" /> - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + id="text25">Blink + A BENCHMARK FOR LARGE-SCALE INTERCONNECTION NETWORKS - Blink - A BENCHMARK FOR LARGE-SCALE INTERCONNECTION NETWORKS From e7d734e95669524703a215b46f7a59dccf8e282a Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Tue, 26 May 2026 20:04:15 +0200 Subject: [PATCH 06/26] [ADD] Logo. --- assets/blink.svg:Zone.Identifier | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 assets/blink.svg:Zone.Identifier diff --git a/assets/blink.svg:Zone.Identifier b/assets/blink.svg:Zone.Identifier deleted file mode 100644 index 92384252a..000000000 --- a/assets/blink.svg:Zone.Identifier +++ /dev/null @@ -1,4 +0,0 @@ -[ZoneTransfer] -ZoneId=3 -ReferrerUrl=https://claude.ai/chat/dbdd6758-5404-48b7-8114-90d42e4c9ae1 -HostUrl=https://claude.ai/api/organizations/85d1dfa7-3ea1-4687-8108-21fcc1c2c2c3/conversations/dbdd6758-5404-48b7-8114-90d42e4c9ae1/wiggle/download-file?path=%2Fmnt%2Fuser-data%2Foutputs%2Fblink-v3-final.svg From 397e83ce94743df3ff7af872b6b7ef9987e71286 Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Tue, 26 May 2026 20:04:59 +0200 Subject: [PATCH 07/26] [ADD] Logo.ush --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index d4f3ab30b..0fec66193 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@
- Blink logo + Blink logo
---- Blink is a collection of MPI benchmarks designed for long-running, in-situ measurement of network behaviour under realistic traffic conditions. Each benchmark runs for a configurable number of iterations (or endlessly until interrupted), records per-iteration latency on every rank, and emits a CSV summary when it finishes — either naturally or via `SIGUSR1`. From 2b29e0abb3c2ae9548eabc4863ddbd8b024ddd5e Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Fri, 5 Jun 2026 20:40:05 +0200 Subject: [PATCH 08/26] [ADD] Fixes and refactoring. --- CMakeLists.txt | 32 +++ README.md | 98 ++++++++- src/allgather/allgather_b.c | 29 ++- src/allgather/allgather_comm_only.cpp | 45 ++-- src/allgather/allgather_nb.c | 29 ++- src/allreduce/allreduce_b.c | 29 ++- src/allreduce/allreduce_nb.c | 27 ++- src/alltoall/alltoall_b.c | 56 ++--- src/alltoall/alltoall_comm_only.cpp | 74 +++---- src/alltoall/alltoall_man.c | 43 ++-- src/alltoall/alltoall_nb.c | 32 ++- src/barrier/barrier_b.c | 18 +- src/barrier/barrier_nb.c | 18 +- src/broadcast/broadcast_b.c | 35 +++- src/broadcast/broadcast_nb.c | 35 +++- src/common.h | 279 +++++++++++++++++++------ src/gather/gather_b.c | 30 ++- src/gather/gather_nb.c | 30 ++- src/incast/incast_b.c | 55 +++-- src/incast/incast_bsnbr.c | 53 ++++- src/incast/incast_get.c | 64 ++++-- src/incast/incast_nb.c | 53 ++++- src/incast/incast_put.c | 63 ++++-- src/kpartners/kpartners_nb.c | 128 ++++++++++-- src/pairwise/pairwise_b.c | 48 ++++- src/pairwise/pairwise_bsnbr.c | 52 +++-- src/pairwise/pairwise_nb.c | 52 +++-- src/pingpong/pingpong_b.c | 65 +++--- src/pingpong/pingpong_pairwise_b.c | 28 ++- src/reduce/reduce_b.c | 27 ++- src/reduce/reduce_nb.c | 27 ++- src/reduce_scatter/reduce_scatter_b.c | 29 ++- src/reduce_scatter/reduce_scatter_nb.c | 29 ++- src/ring/ring_bsnbr.c | 74 +++++-- src/ring/ring_nb.c | 75 +++++-- src/scatter/scatter_b.c | 30 ++- src/scatter/scatter_nb.c | 30 ++- src/stencil/stencil_2d_nb.c | 34 ++- src/tools/checker.c | 99 ++++----- tests/CMakeLists.txt | 33 +++ tests/popen_helpers.h | 222 ++++++++++++++++++++ tests/test_collectives.cpp | 186 +++++++++++++++++ tests/test_incast.cpp | 275 ++++++++++++++++++++++++ tests/test_kpartners.cpp | 72 +++++++ tests/test_misc.cpp | 208 ++++++++++++++++++ tests/test_pairwise.cpp | 251 ++++++++++++++++++++++ tests/test_pingpong.cpp | 78 +++++++ tests/test_ring.cpp | 232 ++++++++++++++++++++ tests/test_stencil.cpp | 140 +++++++++++++ 49 files changed, 3161 insertions(+), 590 deletions(-) create mode 100644 tests/CMakeLists.txt create mode 100644 tests/popen_helpers.h create mode 100644 tests/test_collectives.cpp create mode 100644 tests/test_incast.cpp create mode 100644 tests/test_kpartners.cpp create mode 100644 tests/test_misc.cpp create mode 100644 tests/test_pairwise.cpp create mode 100644 tests/test_pingpong.cpp create mode 100644 tests/test_ring.cpp create mode 100644 tests/test_stencil.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f658bb8e..1e328f8a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,3 +15,35 @@ add_compile_options(-O3) add_compile_definitions(_GNU_SOURCE) add_subdirectory(src) + +option(BLINK_TESTS "Build correctness tests (requires GTest)" ON) + +if(BLINK_TESTS) + find_package(GTest QUIET) + if(NOT GTest_FOUND) + message(STATUS "GTest not found via find_package — fetching with FetchContent") + include(FetchContent) + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.14.0 + ) + set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) + FetchContent_MakeAvailable(googletest) + if(NOT TARGET GTest::GTest) + add_library(GTest::GTest ALIAS gtest) + add_library(GTest::Main ALIAS gtest_main) + endif() + set(GTest_FOUND TRUE) + endif() + + if(GTest_FOUND) + enable_testing() + add_subdirectory(tests) + message(STATUS "GTest ready — correctness tests enabled (run: ctest --test-dir build)") + else() + message(WARNING "GTest unavailable — correctness tests skipped (pass -DBLINK_TESTS=OFF to silence)") + endif() +else() + message(STATUS "BLINK_TESTS=OFF — correctness tests skipped") +endif() diff --git a/README.md b/README.md index 0fec66193..0e95bda4a 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Most MPI benchmark suites run a fixed iteration sweep and report a single aggreg **Temporal evolution.** Blink records one measurement per iteration rather than collapsing everything into a final summary. This lets you observe how latency evolves over time, detect jitter events, and correlate performance spikes with external activity on the system. -**Burst traffic modeling.** Real applications rarely issue communication at a steady, uniform rate. Blink supports configurable burst-pause cycles, including exponentially distributed burst lengths and pause durations, so the generated traffic pattern can more closely reflect the bursty nature of production workloads. +**Burst traffic modeling.** Real applications rarely issue communication at a steady, uniform rate. Blink supports configurable burst-pause cycles with pluggable inter-arrival distributions — exponential, Pareto, and log-normal — so the generated traffic pattern can more closely reflect the bursty, heavy-tailed nature of production workloads. **Long-running operation.** Blink is designed to run alongside real workloads or as a background monitor. It can run endlessly (`-endl`), keeps only the most recent *N* samples in a ring buffer (`-maxsamples`), and responds to `SIGUSR1` with a clean shutdown — collecting and printing whatever has been measured so far before calling `MPI_Finalize`. @@ -26,6 +26,54 @@ cmake --build build -j$(nproc) Binaries are placed in `build/bin/`. Adding a new `.c` or `.cpp` file anywhere under `src/` is enough — CMake picks it up automatically on the next configure. + +## Testing + +Correctness tests are built automatically alongside the benchmarks (requires GTest >= 1.14, fetched automatically if not found on the system). Pass `-DBLINK_TESTS=OFF` to skip them. + +Each test is a single-process GTest binary that spawns the actual benchmark under `mpirun -n N -debug` via `popen()` and parses structured `DEBUG key=val` output. Most suites require **8 MPI ranks**; `test_pingpong` also covers a 2-rank case for `pingpong_b`. + +### Run all tests + +```bash +ctest --test-dir build --output-on-failure +``` + +| Useful flags | Effect | +|---|---| +| `-j ` | Run up to N test suites in parallel | +| `-V` | Always print test output (verbose) | +| `--output-on-failure` | Print output only on failure | +| `-R ` | Run only suites whose name matches | + +### Run a single test suite + +```bash +ctest --test-dir build -R test_collectives --output-on-failure +``` + +### Test suites + +| Suite | Benchmarks covered | Properties verified | +|---|---|---| +| `test_collectives` | `allgather_b/nb/comm_only`, `allreduce_b/nb`, `alltoall_b/nb/man/comm_only`, `barrier_b/nb`, `broadcast_b/nb`, `gather_b/nb`, `scatter_b/nb`, `reduce_b/nb`, `reduce_scatter_b/nb` | Coverage, communicator size, root rank, data integrity (plain + burst), b/nb agreement, allgather transfer-volume agreement | +| `test_pairwise` | `pairwise_b/nb/bsnbr` | Coverage, validity, symmetry (offpair/rpair), bijection, exact pairs, non-reciprocal modes (perm/rot) (plain + burst) | +| `test_ring` | `ring_nb`, `ring_bsnbr` | Coverage, validity, exact sequential neighbours, consistency (sequential + random), completeness (plain + burst) | +| `test_incast` | `incast_b/nb/bsnbr/get/put` | Coverage, roles (one receiver, N-1 senders), target correctness (plain + burst) | +| `test_pingpong` | `pingpong_b` (2 ranks), `pingpong_pairwise_b` (8 ranks) | Coverage, communicator size, data integrity (plain + burst) | +| `test_kpartners` | `kpartners_nb` | Coverage, communicator size, k value, slot coherence (plain + burst) | +| `test_stencil` | `stencil_2d_nb` | Coverage, communicator size, data integrity, neighbour-graph consistency (plain, periodic, burst) | +| `test_misc` | cross-cutting (`barrier_nb`, `incast_b`, `dist_test`, `alltoall_nb`) | Ring-buffer wrap, warm-up exclusion, pretty-print formatter, `-mrand` determinism, sampler self-check, SIGUSR1 clean shutdown | + +### Overriding MPI launcher + +The test binaries bake in the MPI launcher paths at configure time. Override them at runtime with environment variables: + +```bash +BLINK_MPIEXEC=/opt/mpi/bin/mpirun BLINK_NPROC_FLAG=-np BLINK_BIN_DIR=/custom/bin ctest --test-dir build +``` + + ## Common flags Every benchmark understands the following flags: @@ -41,12 +89,37 @@ Every benchmark understands the following flags: | `-mrank ` | `0` | Rank that collects and prints results | | `-mrand` | off | Pick master rank randomly | | `-seed ` | `1` | RNG seed (shared across ranks) | -| `-blength ` | `0` | Burst length in seconds (0 = single shot per iteration) | -| `-blrand` | off | Randomise burst length (exponential distribution, mean = `-blength`) | -| `-bpause ` | `0` | Pause between bursts in seconds | -| `-bprand` | off | Randomise pause length (exponential distribution, mean = `-bpause`) | +| `-blength ` | `0` | Mean burst length in seconds (0 = single shot per iteration) | +| `-bldist ` | — | Randomise burst length using distribution `D` (`exp`, `pareto`, `lognormal`) | +| `-blshape ` | `1.5` | Shape parameter for burst distribution (α for Pareto, σ for log-normal) | +| `-bpause ` | `0` | Mean pause between bursts in seconds | +| `-bpdist ` | — | Randomise pause length using distribution `D` (`exp`, `pareto`, `lognormal`) | +| `-bpshape ` | `1.5` | Shape parameter for pause distribution (α for Pareto, σ for log-normal) | | `-pretty-print` | off | Human-readable table output instead of CSV (see [Output format](#output-format)) | +### Burst distributions + +When `-bldist` or `-bpdist` is supplied, burst lengths / pauses are drawn independently on each iteration from the chosen distribution, parameterised so that the empirical mean equals `-blength` / `-bpause` regardless of the shape parameter: + +| Distribution | Flag value | Shape parameter | Notes | +|---|---|---|---| +| Exponential | `exp` | — (ignored) | Light-tailed; fully determined by its mean | +| Pareto | `pareto` | α (`-blshape` / `-bpshape`, must be > 1) | Heavy-tailed; α ≤ 2 → infinite variance | +| Log-normal | `lognormal` | σ (`-blshape` / `-bpshape`, > 0) | Log-symmetric; larger σ → heavier tail | + +```bash +# Pareto bursts (α=2, heavy tail) with exponential pauses +mpirun -n 8 build/bin/alltoall_nb -iter 500 \ + -blength 0.01 -bldist pareto -blshape 2.0 \ + -bpause 0.05 -bpdist exp +``` + +The sampler implementations can be verified independently: + +```bash +mpirun -n 1 build/bin/dist_test +``` + ## Benchmarks Benchmarks are grouped by traffic pattern. The suffix convention is: @@ -82,7 +155,7 @@ Every rank broadcasts its buffer; all ranks collect the full result. | `allgather_comm_only` | `MPI_Isend` / `MPI_Irecv` (C++) | #### All-reduce — `allreduce/` -Global reduction (`MPI_SUM` over `double`) delivered to all ranks. +Global reduction (`MPI_SUM` over `int`) delivered to all ranks. | Binary | MPI primitive | |--------|--------------| @@ -121,7 +194,7 @@ Root distributes a distinct chunk to every rank. | `scatter_nb` | `MPI_Iscatter` | #### Reduce — `reduce/` -Global reduction (`MPI_SUM` over `double`) to root only. +Global reduction (`MPI_SUM` over `int`) to root only. | Binary | MPI primitive | |--------|--------------| @@ -168,10 +241,17 @@ Each rank exchanges messages with exactly one partner. Partners are chosen by o | `pairwise_nb` | Non-blocking (`MPI_Isend` / `MPI_Irecv`) | | `pairwise_bsnbr` | Non-blocking send, blocking receive | -Extra flags: `-offset ` (default `1`) — fixed partner offset; `-mode ` (default `offpair`) — pairing mode (`offpair` for fixed offset, `rand` for random pairing). +Extra flags: `-offset ` (default `1`) — fixed partner offset; `-mode ` (default `offpair`) — pairing mode. Supported modes: + +| Mode | Meaning | +|------|---------| +| `offpair` | Disjoint reciprocal pairs at fixed offset `O` (e.g. `(0,1) (2,3) …`) | +| `rpair` | Uniformly random disjoint pairs (same seed on all ranks) | +| `perm` | Random permutation: each rank sends to one partner (not necessarily reciprocal) | +| `rot` | Ring rotation: rank `i` sends to `(i+O) mod n` | #### Ring — `ring/` -Each rank sends to its right neighbour and receives from its left. +Each rank simultaneously sends to both its left and right neighbours and receives one message from each (bidirectional ring exchange). | Binary | Variant | |--------|---------| diff --git a/src/allgather/allgather_b.c b/src/allgather/allgather_b.c index b7c4fdc19..158b857bc 100644 --- a/src/allgather/allgather_b.c +++ b/src/allgather/allgather_b.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -37,12 +37,12 @@ int main(int argc, char** argv){ sched_setaffinity(0, sizeof(mask), &mask);*/ /*allocate buffers*/ - int send_buf_size, recv_buf_size; + size_t send_buf_size, recv_buf_size; unsigned char *send_buf; unsigned char *recv_buf; send_buf_size=msg_size; - recv_buf_size=w_size*msg_size; + recv_buf_size=(size_t)w_size*msg_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -50,12 +50,12 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -108,6 +110,17 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _r, _b, _ok = 1; + for (_r = 0; _r < w_size && _ok; _r++) + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[(size_t)_r * msg_size + _b] != (unsigned char)_r) _ok = 0; + printf("DEBUG rank=%d nprocs=%d bytes=%zu check=%s\n", + my_rank, w_size, (size_t)send_buf_size, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/allgather/allgather_comm_only.cpp b/src/allgather/allgather_comm_only.cpp index 61979206d..a405e87b2 100644 --- a/src/allgather/allgather_comm_only.cpp +++ b/src/allgather/allgather_comm_only.cpp @@ -39,16 +39,15 @@ static inline int copy_buffer_different_dt (const void *input_buffer, size_t sco void allgather_memcpy(const void *sbuf, size_t scount, MPI_Datatype sdtype, void* rbuf, size_t rcount, MPI_Datatype rdtype, MPI_Comm comm){ - int rank, size, sendto, recvfrom, i, recvdatafrom, senddatafrom; + int rank; MPI_Aint rlb, rext; char *tmpsend = NULL, *tmprecv = NULL; - MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank); MPI_Type_get_extent(rdtype, &rlb, &rext); - tmprecv = (char*) rbuf + rank * rcount * rext; + tmprecv = (char*) rbuf + (size_t)rank * rcount * rext; if (MPI_IN_PLACE != sbuf) { tmpsend = (char*) sbuf; copy_buffer_different_dt(tmpsend, scount, sdtype, tmprecv, rcount, rdtype); @@ -95,15 +94,15 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ - int i, j, k; + int i, k; argc = parse_common_args(argc, argv); for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -122,13 +121,15 @@ int main(int argc, char** argv){ if(msg_size%sizeof(int)!=0){ if(my_rank==master_rank){ fprintf(stderr, "Msg-size (%d) must be divisible by size of int (%ld)",msg_size,sizeof(int)); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } - - send_buf_size=msg_size/w_size; - msg_size_ints=send_buf_size/sizeof(int); - recv_buf_size=msg_size; + + /* -msgsize is the per-rank contribution (matches allgather_b/nb); the full + * gathered result is w_size * msg_size bytes. */ + send_buf_size=msg_size; + msg_size_ints=(size_t)msg_size/sizeof(int); + recv_buf_size=(size_t)w_size*msg_size; send_buf=(int*)malloc_align(send_buf_size); recv_buf=(int*)malloc_align(recv_buf_size); @@ -136,12 +137,12 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ for(i=0;i= warm_up_iters) record_duration(measure_total_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -199,6 +202,18 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + size_t _r, _b; + int _ok = 1; + for (_r = 0; _r < (size_t)w_size && _ok; _r++) + for (_b = 0; _b < msg_size_ints && _ok; _b++) + if (recv_buf[_r * msg_size_ints + _b] != (int)_r) _ok = 0; + printf("DEBUG rank=%d nprocs=%d bytes=%zu check=%s\n", + my_rank, w_size, (size_t)send_buf_size, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/allgather/allgather_nb.c b/src/allgather/allgather_nb.c index a9e2db1cc..9cb89fa8b 100644 --- a/src/allgather/allgather_nb.c +++ b/src/allgather/allgather_nb.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -37,13 +37,13 @@ int main(int argc, char** argv){ sched_setaffinity(0, sizeof(mask), &mask);*/ /*allocate buffers*/ - int send_buf_size, recv_buf_size; + size_t send_buf_size, recv_buf_size; unsigned char *send_buf; unsigned char *recv_buf; MPI_Request *requests; send_buf_size=msg_size; - recv_buf_size=w_size*msg_size; + recv_buf_size=(size_t)w_size*msg_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -52,12 +52,12 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || durations==NULL || requests==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -111,6 +113,17 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _r, _b, _ok = 1; + for (_r = 0; _r < w_size && _ok; _r++) + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[(size_t)_r * msg_size + _b] != (unsigned char)_r) _ok = 0; + printf("DEBUG rank=%d nprocs=%d bytes=%zu check=%s\n", + my_rank, w_size, (size_t)send_buf_size, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/allreduce/allreduce_b.c b/src/allreduce/allreduce_b.c index 0aaaa10b4..3dfc71eb0 100644 --- a/src/allreduce/allreduce_b.c +++ b/src/allreduce/allreduce_b.c @@ -18,15 +18,15 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ - int i, j, k; + int i, k; argc = parse_common_args(argc, argv); for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -45,7 +45,7 @@ int main(int argc, char** argv){ if(msg_size%sizeof(int)!=0){ if(my_rank==master_rank){ fprintf(stderr, "Msg-size (%d) must be divisible by size of int (%ld)",msg_size,sizeof(int)); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -59,12 +59,12 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -118,6 +120,15 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _e = w_size * (w_size - 1) / 2, _ok = 1, _b; + for (_b = 0; _b < msg_size_ints && _ok; _b++) + if (recv_buf[_b] != _e) _ok = 0; + printf("DEBUG rank=%d nprocs=%d check=%s\n", my_rank, w_size, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/allreduce/allreduce_nb.c b/src/allreduce/allreduce_nb.c index 035e0bc00..73824b261 100644 --- a/src/allreduce/allreduce_nb.c +++ b/src/allreduce/allreduce_nb.c @@ -18,15 +18,15 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ - int i, j, k; + int i, k; argc = parse_common_args(argc, argv); for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -46,7 +46,7 @@ int main(int argc, char** argv){ if(msg_size%sizeof(int)!=0){ if(my_rank==master_rank){ fprintf(stderr, "Msg-size (%d) must be divisible by size of int (%ld)",msg_size,sizeof(int)); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -61,12 +61,12 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || requests==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -121,6 +123,15 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _e = w_size * (w_size - 1) / 2, _ok = 1, _b; + for (_b = 0; _b < msg_size_ints && _ok; _b++) + if (recv_buf[_b] != _e) _ok = 0; + printf("DEBUG rank=%d nprocs=%d check=%s\n", my_rank, w_size, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/alltoall/alltoall_b.c b/src/alltoall/alltoall_b.c index 66b92b479..82bbb889a 100644 --- a/src/alltoall/alltoall_b.c +++ b/src/alltoall/alltoall_b.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -37,12 +37,12 @@ int main(int argc, char** argv){ sched_setaffinity(0, sizeof(mask), &mask);*/ /*allocate buffers*/ - int send_buf_size, recv_buf_size; + size_t send_buf_size, recv_buf_size; unsigned char *send_buf; unsigned char *recv_buf; - send_buf_size=msg_size*w_size; - recv_buf_size=msg_size*w_size; + send_buf_size=(size_t)msg_size*w_size; + recv_buf_size=(size_t)msg_size*w_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -50,12 +50,12 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ - for(i=0;i= 8 && msg_size % 8 == 0){ // Check if I can use 64-bit data types - large_count = msg_size / 8; - if (large_count >= ((u_int64_t) (1UL << 32)) - 1) { // If large_count can't be represented on 32 bits - if(my_rank == 0){ - printf("\tTransfer size (B): -1, Transfer Time (s): -1, Bandwidth (GB/s): -1, Iteration -1\n"); - } - return -1; - } - }else{ - if (msg_size >= ((u_int64_t) (1UL << 32)) - 1) { // If msg_size can't be represented on 32 bits - if(my_rank == 0){ - printf("\tTransfer size (B): -1, Transfer Time (s): -1, Bandwidth (GB/s): -1, Iteration -1\n"); - } - return -1; - } - } + measured_iters=0; MPI_Barrier(MPI_COMM_WORLD); do{ for(k=0;k= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -129,6 +109,16 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _r, _b, _ok = 1; + for (_r = 0; _r < w_size && _ok; _r++) + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[(size_t)_r * msg_size + _b] != (unsigned char)_r) _ok = 0; + printf("DEBUG rank=%d nprocs=%d check=%s\n", my_rank, w_size, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/alltoall/alltoall_comm_only.cpp b/src/alltoall/alltoall_comm_only.cpp index 33bfbcd75..c866742df 100644 --- a/src/alltoall/alltoall_comm_only.cpp +++ b/src/alltoall/alltoall_comm_only.cpp @@ -25,11 +25,10 @@ void all2all_memcpy(const void* sendbuf, int sendcount, MPI_Datatype sendtype, v const char* sbuf = static_cast(sendbuf); char* rbuf = static_cast(recvbuf); - double mem_time = MPI_Wtime(); // Copy local data directly (self-send) - memcpy(rbuf + rank * datatype_size * recvcount, - sbuf + rank * datatype_size * sendcount, - sendcount * datatype_size); + memcpy(rbuf + (size_t)rank * datatype_size * recvcount, + sbuf + (size_t)rank * datatype_size * sendcount, + (size_t)sendcount * datatype_size); } @@ -70,7 +69,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -78,7 +77,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -93,8 +92,8 @@ int main(int argc, char** argv){ unsigned char *send_buf; unsigned char *recv_buf; - send_buf_size=msg_size*w_size; - recv_buf_size=msg_size*w_size; + send_buf_size=(size_t)msg_size*w_size; + recv_buf_size=(size_t)msg_size*w_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -102,18 +101,18 @@ int main(int argc, char** argv){ if(send_buf==NULL){ fprintf(stderr,"Failed to allocate send_buf on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } else if (recv_buf==NULL){ fprintf(stderr,"Failed to allocate recv_buf on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } else if (durations==NULL){ fprintf(stderr,"Failed to allocate durations buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ - for(i=0;i= 8 && msg_size % 8 == 0){ // Check if I can use 64-bit data types - large_count = msg_size / 8; - if (large_count >= ((u_int64_t) (1UL << 32)) - 1) { // If large_count can't be represented on 32 bits - if(my_rank == 0){ - printf("\tTransfer size (B): -1, Transfer Time (s): -1, Bandwidth (GB/s): -1, Iteration -1\n"); - } - return -1; - } - }else{ - if (msg_size >= ((u_int64_t) (1UL << 32)) - 1) { // If msg_size can't be represented on 32 bits - if(my_rank == 0){ - printf("\tTransfer size (B): -1, Transfer Time (s): -1, Bandwidth (GB/s): -1, Iteration -1\n"); - } - return -1; - } - } + measured_iters=0; MPI_Barrier(MPI_COMM_WORLD); do{ for(k=0;k= warm_up_iters) record_duration(measure_total_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -194,6 +170,16 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _r, _b, _ok = 1; + for (_r = 0; _r < w_size && _ok; _r++) + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[(size_t)_r * msg_size + _b] != (unsigned char)_r) _ok = 0; + printf("DEBUG rank=%d nprocs=%d check=%s\n", my_rank, w_size, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/alltoall/alltoall_man.c b/src/alltoall/alltoall_man.c index 085984e32..6c7b0629c 100644 --- a/src/alltoall/alltoall_man.c +++ b/src/alltoall/alltoall_man.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ int i, j, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -37,14 +37,14 @@ int main(int argc, char** argv){ sched_setaffinity(0, sizeof(mask), &mask);*/ /*allocate buffers*/ - int send_buf_size, recv_buf_size; + size_t send_buf_size, recv_buf_size; unsigned char *send_buf; unsigned char *recv_buf; MPI_Request *send_requests; MPI_Request *recv_requests; - - send_buf_size=w_size*msg_size; - recv_buf_size=w_size*msg_size; + + send_buf_size=(size_t)w_size*msg_size; + recv_buf_size=(size_t)w_size*msg_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -54,12 +54,12 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || durations==NULL || send_requests==NULL || recv_requests==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ - for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -121,16 +123,17 @@ int main(int argc, char** argv){ } } }while(endless); - - /* - int l; - printf("%d\n",my_rank); - for(l=0;l= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -111,6 +113,16 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _r, _b, _ok = 1; + for (_r = 0; _r < w_size && _ok; _r++) + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[(size_t)_r * msg_size + _b] != (unsigned char)_r) _ok = 0; + printf("DEBUG rank=%d nprocs=%d check=%s\n", my_rank, w_size, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/barrier/barrier_b.c b/src/barrier/barrier_b.c index 42dc6726a..55ab2f6b8 100644 --- a/src/barrier/barrier_b.c +++ b/src/barrier/barrier_b.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -41,7 +41,7 @@ int main(int argc, char** argv){ if(durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*print basic info to stdout*/ @@ -59,12 +59,14 @@ int main(int argc, char** argv){ double measure_start_time; double burst_length_mean=burst_length; double burst_pause_mean=burst_pause; - bool burst_cont=false; + int burst_cont=0; curr_iters=0; + measured_iters=0; MPI_Barrier(MPI_COMM_WORLD); do{ for(k=0;k= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -93,6 +95,12 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + printf("DEBUG rank=%d nprocs=%d check=OK\n", my_rank, w_size); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/barrier/barrier_nb.c b/src/barrier/barrier_nb.c index af4f04929..1940fe821 100644 --- a/src/barrier/barrier_nb.c +++ b/src/barrier/barrier_nb.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -44,7 +44,7 @@ int main(int argc, char** argv){ if(durations==NULL || requests==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*print basic info to stdout*/ @@ -63,12 +63,14 @@ int main(int argc, char** argv){ double measure_start_time; double burst_length_mean=burst_length; double burst_pause_mean=burst_pause; - bool burst_cont=false; + int burst_cont=0; curr_iters=0; + measured_iters=0; MPI_Barrier(MPI_COMM_WORLD); do{ for(k=0;k= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -98,6 +100,12 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + printf("DEBUG rank=%d nprocs=%d check=OK\n", my_rank, w_size); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/broadcast/broadcast_b.c b/src/broadcast/broadcast_b.c index 86931c211..588137d14 100644 --- a/src/broadcast/broadcast_b.c +++ b/src/broadcast/broadcast_b.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -37,23 +37,23 @@ int main(int argc, char** argv){ sched_setaffinity(0, sizeof(mask), &mask);*/ /*allocate buffers*/ - int buf_size; + size_t buf_size; unsigned char *buf; - - buf_size=msg_size*measure_granularity;; - + + buf_size=(size_t)msg_size*measure_granularity; + buf=(unsigned char*)malloc_align(buf_size); durations=(double *)malloc_align(sizeof(double)*max_samples); if(buf==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ if(my_rank==master_rank){ - for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -107,6 +109,17 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _ok = 1; + unsigned char _fill = (unsigned char)master_rank; + for (size_t _b = 0; _b < buf_size && _ok; _b++) + if (buf[_b] != _fill) _ok = 0; + printf("DEBUG rank=%d nprocs=%d root=%d check=%s\n", + my_rank, w_size, master_rank, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/broadcast/broadcast_nb.c b/src/broadcast/broadcast_nb.c index 8319056c2..0ac22fdaf 100644 --- a/src/broadcast/broadcast_nb.c +++ b/src/broadcast/broadcast_nb.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -37,25 +37,25 @@ int main(int argc, char** argv){ sched_setaffinity(0, sizeof(mask), &mask);*/ /*allocate buffers*/ - int buf_size; + size_t buf_size; unsigned char *buf; MPI_Request *requests; - - buf_size=msg_size*measure_granularity;; - + + buf_size=(size_t)msg_size*measure_granularity; + buf=(unsigned char*)malloc_align(buf_size); requests=(MPI_Request*)malloc_align(sizeof(MPI_Request)*(measure_granularity)); durations=(double *)malloc_align(sizeof(double)*max_samples); if(buf==NULL || durations==NULL || requests==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ if(my_rank==master_rank){ - for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -110,6 +112,17 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _ok = 1; + unsigned char _fill = (unsigned char)master_rank; + for (size_t _b = 0; _b < buf_size && _ok; _b++) + if (buf[_b] != _fill) _ok = 0; + printf("DEBUG rank=%d nprocs=%d root=%d check=%s\n", + my_rank, w_size, master_rank, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/common.h b/src/common.h index 517e058b3..eb092037d 100644 --- a/src/common.h +++ b/src/common.h @@ -16,7 +16,7 @@ static double rand_exp(double mean, double shape) { (void)shape; - double u = rand() / (RAND_MAX + 1.0); + double u = (rand() + 0.5) / (RAND_MAX + 1.0); /* shift away from 0 and 1 */ return -mean * log(1.0 - u); } @@ -30,6 +30,10 @@ static double rand_pareto(double mean, double shape) fprintf(stderr, "rand_pareto: shape (alpha) must be > 1, got %g\n", alpha); exit(-1); } + if (mean <= 0.0) { + fprintf(stderr, "rand_pareto: mean must be > 0, got %g\n", mean); + exit(-1); + } double x_m = mean * (alpha - 1.0) / alpha; double u = (rand() + 0.5) / (RAND_MAX + 1.0); /* shift away from 0 */ return x_m * pow(u, -1.0 / alpha); @@ -40,6 +44,14 @@ static double rand_pareto(double mean, double shape) * Box-Muller transform. */ static double rand_lognormal(double mean, double sigma) { + if (mean <= 0.0) { + fprintf(stderr, "rand_lognormal: mean must be > 0, got %g\n", mean); + exit(-1); + } + if (sigma <= 0.0) { + fprintf(stderr, "rand_lognormal: sigma must be > 0, got %g\n", sigma); + exit(-1); + } double mu = log(mean) - 0.5 * sigma * sigma; double u1 = (rand() + 0.5) / (RAND_MAX + 1.0); /* shift away from 0 */ double u2 = (rand() + 0.5) / (RAND_MAX + 1.0); @@ -65,7 +77,7 @@ static int dsleep(double t) } /*double comparison function for quicksort*/ -int compare_doubles(const void *p1, const void *p2) +static int compare_doubles(const void *p1, const void *p2) { if (*(double *)p1 < *(double *)p2) return -1; @@ -79,11 +91,18 @@ int compare_doubles(const void *p1, const void *p2) static int my_rank; static int w_size; static int master_rank = 0; -static int curr_iters; +static int curr_iters; /* total outer iterations executed (warmup + measured) */ +static int measured_iters; /* number of recorded samples (excludes warmup) */ static int warm_up_iters = 5; static int max_samples = 1000; static double *durations; +/* Async-signal-safe shutdown flag set by sig_handler; checked in measurement + * loops. The signal handler does no MPI / no malloc / no stdio — only sets + * this flag. The main thread observes it between iterations and exits the + * loop cleanly, then calls write_results() + MPI_Finalize() itself. */ +static volatile sig_atomic_t shutdown_requested = 0; + /* common benchmark parameters – initialised to defaults, set by parse_common_args() */ static int msg_size = 1024; static int measure_granularity = 1; @@ -95,6 +114,7 @@ static int burst_length_rand = 0; /* set by -bldist */ static double burst_pause = 0.0; static int burst_pause_rand = 0; /* set by -bpdist */ static int pretty_output = 0; +static int debug_mode = 0; /* distribution selection for burst length and pause (-bldist / -bpdist). * Values: "exp", "pareto", "lognormal". shape: α for Pareto, σ for log-normal @@ -104,6 +124,43 @@ static double burst_shape = 1.5; static char pause_dist[16] = "exp"; static double pause_shape = 1.5; +/* Bounds-checked accessor for a flag's value argument. Aborts with a clear + * message instead of dereferencing argv[argc] (== NULL) when a value-taking + * flag is supplied as the final command-line token. */ +static const char *arg_value(int argc, char **argv, int *i) +{ + if (*i + 1 >= argc) { + if (my_rank == master_rank) + fprintf(stderr, "Missing value for option %s\n", argv[*i]); + MPI_Abort(MPI_COMM_WORLD, -1); + } + return argv[++(*i)]; +} + +/* Validate a distribution name / shape pair selected via -bldist/-bpdist so a + * misconfiguration fails loudly at startup rather than silently producing + * garbage durations later (e.g. log-normal with a non-positive mean). */ +static void validate_burst_dist(const char *what, const char *dist, + double mean, double shape) +{ + if (mean <= 0.0) { + if (my_rank == master_rank) + fprintf(stderr, "%s: randomised distribution requires a positive mean, got %g\n", + what, mean); + MPI_Abort(MPI_COMM_WORLD, -1); + } + if (strcmp(dist, "pareto") == 0 && shape <= 1.0) { + if (my_rank == master_rank) + fprintf(stderr, "%s: pareto shape (alpha) must be > 1, got %g\n", what, shape); + MPI_Abort(MPI_COMM_WORLD, -1); + } + if (strcmp(dist, "lognormal") == 0 && shape <= 0.0) { + if (my_rank == master_rank) + fprintf(stderr, "%s: lognormal shape (sigma) must be > 0, got %g\n", what, shape); + MPI_Abort(MPI_COMM_WORLD, -1); + } +} + /* * Parse the standard set of command-line flags shared by every benchmark. * Unrecognised flags are compacted to the front of argv (after argv[0]) and @@ -117,29 +174,49 @@ static int parse_common_args(int argc, char **argv) int i; for (i = 1; i < argc; i++) { - if (strcmp(argv[i], "-mrank") == 0) { master_rank = atoi(argv[++i]); } + if (strcmp(argv[i], "-mrank") == 0) { master_rank = atoi(arg_value(argc, argv, &i)); } else if (strcmp(argv[i], "-mrand") == 0) { do_rand_master = 1; } - else if (strcmp(argv[i], "-msgsize") == 0) { msg_size = atoi(argv[++i]); } + else if (strcmp(argv[i], "-msgsize") == 0) { msg_size = atoi(arg_value(argc, argv, &i)); } else if (strcmp(argv[i], "-endl") == 0) { endless = 1; } - else if (strcmp(argv[i], "-iter") == 0) { max_iters = atoi(argv[++i]); } - else if (strcmp(argv[i], "-warmup") == 0) { warm_up_iters = atoi(argv[++i]); } - else if (strcmp(argv[i], "-blength") == 0) { burst_length = atof(argv[++i]); } - else if (strcmp(argv[i], "-bpause") == 0) { burst_pause = atof(argv[++i]); } - else if (strcmp(argv[i], "-bldist") == 0) { strncpy(burst_dist, argv[++i], 15); + else if (strcmp(argv[i], "-iter") == 0) { max_iters = atoi(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-warmup") == 0) { warm_up_iters = atoi(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-blength") == 0) { burst_length = atof(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-bpause") == 0) { burst_pause = atof(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-bldist") == 0) { strncpy(burst_dist, arg_value(argc, argv, &i), 15); burst_dist[15] = '\0'; burst_length_rand = 1; } - else if (strcmp(argv[i], "-bpdist") == 0) { strncpy(pause_dist, argv[++i], 15); + else if (strcmp(argv[i], "-bpdist") == 0) { strncpy(pause_dist, arg_value(argc, argv, &i), 15); pause_dist[15] = '\0'; burst_pause_rand = 1; } - else if (strcmp(argv[i], "-blshape") == 0) { burst_shape = atof(argv[++i]); } - else if (strcmp(argv[i], "-bpshape") == 0) { pause_shape = atof(argv[++i]); } - else if (strcmp(argv[i], "-seed") == 0) { rand_seed = atoi(argv[++i]); } - else if (strcmp(argv[i], "-grty") == 0) { measure_granularity = atoi(argv[++i]); } - else if (strcmp(argv[i], "-maxsamples") == 0) { max_samples = atoi(argv[++i]); } + else if (strcmp(argv[i], "-blshape") == 0) { burst_shape = atof(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-bpshape") == 0) { pause_shape = atof(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-seed") == 0) { rand_seed = atoi(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-grty") == 0) { measure_granularity = atoi(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-maxsamples") == 0) { max_samples = atoi(arg_value(argc, argv, &i)); } else if (strcmp(argv[i], "-pretty-print") == 0) { pretty_output = 1; } + else if (strcmp(argv[i], "-debug") == 0) { debug_mode = 1; } else { argv[new_argc++] = argv[i]; } /* pass through unknown flags */ } + /* fail fast on nonsensical numeric parameters */ + if (max_samples < 1) { + if (my_rank == master_rank) + fprintf(stderr, "-maxsamples must be >= 1, got %d\n", max_samples); + MPI_Abort(MPI_COMM_WORLD, -1); + } + if (measure_granularity < 1) { + if (my_rank == master_rank) + fprintf(stderr, "-grty must be >= 1, got %d\n", measure_granularity); + MPI_Abort(MPI_COMM_WORLD, -1); + } + + /* a randomised burst/pause distribution needs a positive mean and a valid + * shape — validate now so we never feed log(0)/negative values to a sampler */ + if (burst_length_rand) + validate_burst_dist("-bldist", burst_dist, burst_length, burst_shape); + if (burst_pause_rand) + validate_burst_dist("-bpdist", pause_dist, burst_pause, pause_shape); + /* seed RNG (shared across all ranks) and resolve randomised master */ srand(rand_seed); if (do_rand_master) @@ -152,9 +229,23 @@ static int parse_common_args(int argc, char **argv) static double sample_burst_length(double mean) { return rand_duration(mean, burst_dist, burst_shape); } static double sample_pause_length(double mean) { return rand_duration(mean, pause_dist, pause_shape); } +/* Record a single measured-iteration latency into the ring buffer. + * Skips writes during warm-up: callers gate this by `if (k >= warm_up_iters)` + * at the outer iteration level — see the standard benchmark loop. + * + * The ring buffer of size `max_samples` therefore only ever holds measured + * samples (no warmup pollution); the (oldest → newest) ordering is + * (measured_iters % max_samples) ... (measured_iters - 1) % max_samples. */ +static inline void record_duration(double t) +{ + durations[measured_iters % max_samples] = t; + measured_iters++; +} + /* ── output helpers ──────────────────────────────────────────────────────── */ -/*format a duration (seconds) into a fixed 12-char string with auto-scaled units*/ +/*format a duration (seconds) with auto-scaled units; "%9.2f UU" is normally + * 12 chars wide (9 is a minimum field width, so extreme outliers can exceed it)*/ static void format_duration(char *buf, size_t len, double t) { if (t < 1e-3) @@ -174,29 +265,42 @@ static void write_results() int start_index; double *tmp_buf = NULL; - if (curr_iters > max_samples) /* wrapped the sampling ring buffer */ + if (measured_iters > max_samples) /* wrapped the sampling ring buffer */ { num_samples = max_samples; - start_index = curr_iters % max_samples; - tmp_buf = (double *)malloc(sizeof(double)*num_samples); - /* copy in chronological order */ - memcpy(tmp_buf, &(durations[start_index]), sizeof(double)*(num_samples - start_index)); - memcpy(&tmp_buf[num_samples - start_index], durations, sizeof(double)*start_index); + start_index = measured_iters % max_samples; } else { - num_samples = curr_iters - warm_up_iters; - start_index = warm_up_iters; - tmp_buf = (double *)malloc(sizeof(double)*num_samples); - memcpy(tmp_buf, &(durations[start_index]), sizeof(double)*num_samples); + num_samples = measured_iters; + start_index = 0; + } + + /* MPI_Gather below uses num_samples as BOTH send- and recv-count, which is + * only valid if every rank contributes the same count. Ranks normally stay + * in lockstep, but to be robust (e.g. a SIGUSR1 shutdown that reaches ranks + * at slightly different iterations) collectively agree on the common minimum + * so the gather can never mismatch. */ + { + int common_samples = num_samples; + MPI_Allreduce(&num_samples, &common_samples, 1, MPI_INT, MPI_MIN, MPI_COMM_WORLD); + /* keep only the most recent common_samples (drop the oldest extras) */ + start_index += (num_samples - common_samples); + num_samples = common_samples; } - double *all_data = (double *)malloc(sizeof(double)*num_samples*w_size); + tmp_buf = (double *)malloc(sizeof(double) * (num_samples > 0 ? num_samples : 1)); + /* copy in chronological order (oldest → newest) using circular indexing */ + for (i = 0; i < num_samples; i++) { + tmp_buf[i] = durations[(start_index + i) % max_samples]; + } + + double *all_data = (double *)malloc(sizeof(double)*(num_samples > 0 ? num_samples : 1)*w_size); double *sorting_buf = (double *)malloc(sizeof(double)*w_size); if (all_data == NULL || sorting_buf == NULL) { fprintf(stderr, "Failed to allocate a buffer on rank %d\n", my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /* print header before the gather so it appears first */ @@ -265,12 +369,40 @@ static void write_results() free(tmp_buf); } -/*signal handler*/ -void sig_handler(int sig) +/* Async-signal-safe handler: only sets a flag. The measurement loop in each + * benchmark observes the flag and exits cleanly; write_results() and + * MPI_Finalize() happen on the main thread, never from signal context. */ +static void sig_handler(int sig) +{ + (void)sig; + shutdown_requested = 1; +} + +/* Install the SIGUSR1 shutdown handler with sigaction() rather than signal(), + * for portable, persistent (non-one-shot) semantics across platforms. */ +static void install_shutdown_handler(void) +{ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sig_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGUSR1, &sa, NULL); +} + +/* Collectively decide whether to shut down. SIGUSR1 only sets the flag on the + * rank that received it, so a purely local check would make ranks leave the + * measurement loop at different iterations — desynchronising the per-iteration + * collectives and the final MPI_Gather (deadlock / mismatch). This all-reduce + * (called once per outer iteration, OUTSIDE the timed region) guarantees every + * rank breaks on the same iteration. Returns non-zero if any rank requested + * shutdown. */ +static int check_shutdown(void) { - write_results(); - MPI_Finalize(); - exit(0); + int local = (int)shutdown_requested; + int global = 0; + MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MAX, MPI_COMM_WORLD); + return global; } /* ── combinatorics helpers ───────────────────────────────────────────────── */ @@ -297,43 +429,56 @@ static int mod(int a, int b) return c; } -/*produce random pairs*/ +/* Produce a uniformly random matching of [0..n-1] (each entry pairs with + * exactly one other entry). Algorithm: shuffle the index list, then pair + * adjacent elements. This is provably uniform over the set of perfect + * matchings (and far simpler than the prior slot-counting version). + * + * For odd n the last element targets itself (it has no partner). + */ static void random_pairs(int *a, int n) { - int i, j; - for (i = 0; i < n; i++) - a[i] = -1; - if (n % 2 == 1) - { - n--; - a[n] = n; /*if odd last rank targets itself*/ + int i; + int has_self = (n % 2 == 1); + int pair_n = has_self ? n - 1 : n; + int *shuf = (int *)malloc(sizeof(int) * n); + + if (shuf == NULL) { + fprintf(stderr, "random_pairs: malloc failed\n"); + MPI_Abort(MPI_COMM_WORLD, -1); } - int k = 1; - int t; - for (i = 0; i < n; i++) - { - if (a[i] == -1) - { - t = rand() % (n - k); - for (j = i + 1; j < n; j++) - { - if (a[j] == -1) - { - if (t == 0) - { - a[i] = j; - a[j] = i; - k += 2; - break; - } - t--; - } - } - } + for (i = 0; i < n; i++) shuf[i] = i; + permute(shuf, n); + + for (i = 0; i < n; i++) a[i] = -1; + + for (i = 0; i < pair_n; i += 2) { + int x = shuf[i]; + int y = shuf[i + 1]; + a[x] = y; + a[y] = x; } + if (has_self) { + /* odd n: one rank has no partner — target itself */ + int lone = shuf[n - 1]; + a[lone] = lone; + } + free(shuf); } -/*produce fixed offset pairs*/ +/* Produce fixed-offset pairs: walks i from 0 upward and pairs i with (i+o) + * whenever both ranks are still free and the partner is not a wrap-around + * (n - i >= o). This guarantees disjoint pairs (i, i+o), (i+2o, i+3o), … + * — for offset o=1 you get (0,1) (2,3) (4,5)…, for o=2 you get (0,2) + * (1,3) (4,6) (5,7)…, etc. + * + * Only non-wrapping reciprocal pairs (i, i+o) are formed; any rank that cannot + * be matched without wrapping around falls through to a self-pair (a[i] = i, + * i.e. it performs no communication). This happens for every offset > n/2, and + * also for many offsets <= n/2 when n is not a multiple of 2*o (e.g. n=12, o=4 + * leaves ranks 8..11 self-paired). Full disjoint tiling is only guaranteed when + * 2*o divides n (notably o=1). Recommended usage: 1 <= o <= n/2. + */ static void offset_pairs(int *a, int n, int o) { int i, t; @@ -354,7 +499,7 @@ static void offset_pairs(int *a, int n, int o) for (i = 0; i < n; i++) { if (a[i] == -1) - a[i] = i; + a[i] = i; /* no reciprocal partner — self */ } } @@ -365,7 +510,7 @@ static void* malloc_align(size_t size) int ret = posix_memalign(&p, ALIGNMENT, size); if (ret != 0) { fprintf(stderr, "Failed to allocate memory on rank\n"); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } return p; } diff --git a/src/gather/gather_b.c b/src/gather/gather_b.c index ef5c4220a..427471382 100644 --- a/src/gather/gather_b.c +++ b/src/gather/gather_b.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -45,21 +45,21 @@ int main(int argc, char** argv){ durations=(double *)malloc_align(sizeof(double)*max_samples); if(my_rank==master_rank){ - recv_buf=(unsigned char*)malloc_align(w_size*msg_size); + recv_buf=(unsigned char*)malloc_align((size_t)w_size*msg_size); if(recv_buf==NULL){ fprintf(stderr,"Failed to allocate recv_buf on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } if(send_buf==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -112,6 +114,18 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _r, _b, _ok = 1; + if (my_rank == master_rank) + for (_r = 0; _r < w_size && _ok; _r++) + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[(size_t)_r * msg_size + _b] != (unsigned char)_r) _ok = 0; + printf("DEBUG rank=%d nprocs=%d root=%d check=%s\n", + my_rank, w_size, master_rank, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/gather/gather_nb.c b/src/gather/gather_nb.c index e305535b0..dc5013441 100644 --- a/src/gather/gather_nb.c +++ b/src/gather/gather_nb.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -47,21 +47,21 @@ int main(int argc, char** argv){ requests=(MPI_Request*)malloc_align(sizeof(MPI_Request)*measure_granularity); if(my_rank==master_rank){ - recv_buf=(unsigned char*)malloc_align(w_size*msg_size); + recv_buf=(unsigned char*)malloc_align((size_t)w_size*msg_size); if(recv_buf==NULL){ fprintf(stderr,"Failed to allocate recv_buf on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } if(send_buf==NULL || durations==NULL || requests==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -115,6 +117,18 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _r, _b, _ok = 1; + if (my_rank == master_rank) + for (_r = 0; _r < w_size && _ok; _r++) + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[(size_t)_r * msg_size + _b] != (unsigned char)_r) _ok = 0; + printf("DEBUG rank=%d nprocs=%d root=%d check=%s\n", + my_rank, w_size, master_rank, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/incast/incast_b.c b/src/incast/incast_b.c index 821dc31b2..83354b7f1 100644 --- a/src/incast/incast_b.c +++ b/src/incast/incast_b.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ int i, j, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -37,12 +37,12 @@ int main(int argc, char** argv){ sched_setaffinity(0, sizeof(mask), &mask);*/ /*allocate buffers*/ - int send_buf_size, recv_buf_size; + size_t send_buf_size, recv_buf_size; unsigned char *send_buf; unsigned char *recv_buf; send_buf_size=msg_size; - recv_buf_size=(w_size-1)*msg_size; + recv_buf_size=(size_t)(w_size-1)*msg_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -50,16 +50,16 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } - /*fill send buffer with dummies*/ + /*fill send buffer: rank-as-payload under -debug so receivers can verify*/ if(my_rank!=master_rank){ - for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -117,6 +119,35 @@ int main(int argc, char** argv){ } }while(endless); + /* debug: deterministic post-loop exchange that verifies each sender's + * payload arrived intact at the receiver. Uses specific source/tag so + * we can attribute each slot to a known sender. Barrier first to + * drain any measurement-loop ANY_TAG receives so they don't consume + * our debug-tagged sends. */ + if (debug_mode) { + MPI_Barrier(MPI_COMM_WORLD); + int _ok = 1; + if (my_rank == master_rank) { + int s, _b, slot = 0; + for (s = 0; s < w_size; s++) { + if (s == master_rank) continue; + MPI_Recv(&recv_buf[slot*msg_size], msg_size, MPI_BYTE, + s, 0xD, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[(size_t)slot*msg_size + _b] != (unsigned char)s) _ok = 0; + slot++; + } + printf("DEBUG rank=%d nprocs=%d nsenders=%d check=%s\n", + my_rank, w_size, w_size - 1, _ok ? "OK" : "FAIL"); + } else { + MPI_Send(send_buf, msg_size, MPI_BYTE, master_rank, 0xD, MPI_COMM_WORLD); + printf("DEBUG rank=%d nprocs=%d target=%d check=OK\n", + my_rank, w_size, master_rank); + } + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/incast/incast_bsnbr.c b/src/incast/incast_bsnbr.c index b3e6a3511..73bcc9ece 100644 --- a/src/incast/incast_bsnbr.c +++ b/src/incast/incast_bsnbr.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ int i, j, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -37,13 +37,13 @@ int main(int argc, char** argv){ sched_setaffinity(0, sizeof(mask), &mask);*/ /*allocate buffers*/ - int send_buf_size, recv_buf_size; + size_t send_buf_size, recv_buf_size; unsigned char *send_buf; unsigned char *recv_buf; MPI_Request *requests; send_buf_size=msg_size; - recv_buf_size=(w_size-1)*msg_size; + recv_buf_size=(size_t)(w_size-1)*msg_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -52,16 +52,16 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || durations==NULL || requests==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } - /*fill send buffer with dummies*/ + /*fill send buffer: rank-as-payload under -debug so receivers can verify*/ if(my_rank!=master_rank){ for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -119,6 +121,35 @@ int main(int argc, char** argv){ } }while(endless); + /* debug: deterministic post-loop exchange that verifies each sender's + * payload arrived intact at the receiver. Uses specific source/tag so + * we can attribute each slot to a known sender. Barrier first to + * drain any measurement-loop ANY_TAG receives so they don't consume + * our debug-tagged sends. */ + if (debug_mode) { + MPI_Barrier(MPI_COMM_WORLD); + int _ok = 1; + if (my_rank == master_rank) { + int s, _b, slot = 0; + for (s = 0; s < w_size; s++) { + if (s == master_rank) continue; + MPI_Recv(&recv_buf[slot*msg_size], msg_size, MPI_BYTE, + s, 0xD, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[(size_t)slot*msg_size + _b] != (unsigned char)s) _ok = 0; + slot++; + } + printf("DEBUG rank=%d nprocs=%d nsenders=%d check=%s\n", + my_rank, w_size, w_size - 1, _ok ? "OK" : "FAIL"); + } else { + MPI_Send(send_buf, msg_size, MPI_BYTE, master_rank, 0xD, MPI_COMM_WORLD); + printf("DEBUG rank=%d nprocs=%d target=%d check=OK\n", + my_rank, w_size, master_rank); + } + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/incast/incast_get.c b/src/incast/incast_get.c index 0af334963..bfcb5dcc3 100644 --- a/src/incast/incast_get.c +++ b/src/incast/incast_get.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ int i, j, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -37,32 +37,33 @@ int main(int argc, char** argv){ sched_setaffinity(0, sizeof(mask), &mask);*/ /*allocate buffers*/ - int send_buf_size, recv_buf_size; + size_t send_buf_size, recv_buf_size; unsigned char *send_buf; unsigned char *recv_buf; MPI_Win rma_win; send_buf_size=msg_size; - recv_buf_size=w_size*msg_size*measure_granularity; + recv_buf_size=(size_t)w_size*msg_size*measure_granularity; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); durations=(double *)malloc_align(sizeof(double)*max_samples); MPI_Win_create(send_buf,send_buf_size,1,MPI_INFO_NULL,MPI_COMM_WORLD,&rma_win); - - if(send_buf==NULL || recv_buf==NULL || durations==NULL || rma_win==NULL){ + + /* MPI_Win is an opaque handle, not a pointer — do not compare to NULL. + * Window creation errors are surfaced via the runtime error handler. */ + if(send_buf==NULL || recv_buf==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } - - /*fill send buffer with dummies*/ - if(my_rank!=master_rank){ - for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -122,6 +125,27 @@ int main(int argc, char** argv){ } }while(endless); + /* debug: master verifies each sender's window content arrived intact. + * recv_buf is already populated by the last measurement-loop Get, with + * sender j's bytes at offset j*msg_size*measure_granularity. */ + if (debug_mode) { + if (my_rank == master_rank) { + int s, _b, _ok = 1; + for (s = 0; s < w_size && _ok; s++) { + if (s == master_rank) continue; + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[(size_t)s*msg_size*measure_granularity + _b] != (unsigned char)s) _ok = 0; + } + printf("DEBUG rank=%d nprocs=%d nsenders=%d check=%s\n", + my_rank, w_size, w_size - 1, _ok ? "OK" : "FAIL"); + } else { + printf("DEBUG rank=%d nprocs=%d target=%d check=OK\n", + my_rank, w_size, master_rank); + } + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/incast/incast_nb.c b/src/incast/incast_nb.c index e39692a1c..fb10d7e54 100644 --- a/src/incast/incast_nb.c +++ b/src/incast/incast_nb.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ int i, j, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -37,14 +37,14 @@ int main(int argc, char** argv){ sched_setaffinity(0, sizeof(mask), &mask);*/ /*allocate buffers*/ - int send_buf_size, recv_buf_size; + size_t send_buf_size, recv_buf_size; unsigned char *send_buf; unsigned char *recv_buf; MPI_Request *send_requests; MPI_Request *recv_requests; send_buf_size=msg_size; - recv_buf_size=(w_size-1)*msg_size; + recv_buf_size=(size_t)(w_size-1)*msg_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -54,16 +54,16 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || durations==NULL || send_requests==NULL || recv_requests==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } - /*fill send buffer with dummies*/ + /*fill send buffer: rank-as-payload under -debug so receivers can verify*/ if(my_rank!=master_rank){ for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -126,6 +128,35 @@ int main(int argc, char** argv){ } }while(endless); + /* debug: deterministic post-loop exchange that verifies each sender's + * payload arrived intact at the receiver. Uses specific source/tag so + * we can attribute each slot to a known sender. Barrier first to + * drain any measurement-loop ANY_TAG receives so they don't consume + * our debug-tagged sends. */ + if (debug_mode) { + MPI_Barrier(MPI_COMM_WORLD); + int _ok = 1; + if (my_rank == master_rank) { + int s, _b, slot = 0; + for (s = 0; s < w_size; s++) { + if (s == master_rank) continue; + MPI_Recv(&recv_buf[slot*msg_size], msg_size, MPI_BYTE, + s, 0xD, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[(size_t)slot*msg_size + _b] != (unsigned char)s) _ok = 0; + slot++; + } + printf("DEBUG rank=%d nprocs=%d nsenders=%d check=%s\n", + my_rank, w_size, w_size - 1, _ok ? "OK" : "FAIL"); + } else { + MPI_Send(send_buf, msg_size, MPI_BYTE, master_rank, 0xD, MPI_COMM_WORLD); + printf("DEBUG rank=%d nprocs=%d target=%d check=OK\n", + my_rank, w_size, master_rank); + } + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/incast/incast_put.c b/src/incast/incast_put.c index b1a325722..30cbcfe84 100644 --- a/src/incast/incast_put.c +++ b/src/incast/incast_put.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -37,32 +37,34 @@ int main(int argc, char** argv){ sched_setaffinity(0, sizeof(mask), &mask);*/ /*allocate buffers*/ - int send_buf_size, recv_buf_size; + size_t send_buf_size, recv_buf_size; unsigned char *send_buf; unsigned char *recv_buf; MPI_Win rma_win; send_buf_size=msg_size; - recv_buf_size=w_size*msg_size*measure_granularity; + recv_buf_size=(size_t)w_size*msg_size*measure_granularity; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); durations=(double *)malloc_align(sizeof(double)*max_samples); MPI_Win_create(recv_buf, recv_buf_size, 1, MPI_INFO_NULL, MPI_COMM_WORLD, &rma_win); - - if(send_buf==NULL || recv_buf==NULL || durations==NULL || rma_win==NULL){ + + /* MPI_Win is an opaque handle, not a pointer — do not compare to NULL. + * Window creation errors are surfaced via the runtime error handler. */ + if(send_buf==NULL || recv_buf==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } - - /*fill send buffer with dummies*/ + + /*fill send buffer: rank-as-payload under -debug so the master can verify + * delivered RMA contents. */ if(my_rank!=master_rank){ - for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -118,6 +122,27 @@ int main(int argc, char** argv){ } }while(endless); + /* debug: master verifies each sender's Put-payload landed at the right + * offset in its window. Senders contribute nothing here beyond having + * filled send_buf with their rank. */ + if (debug_mode) { + if (my_rank == master_rank) { + int s, _b, _ok = 1; + for (s = 0; s < w_size && _ok; s++) { + if (s == master_rank) continue; + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[(size_t)s*msg_size*measure_granularity + _b] != (unsigned char)s) _ok = 0; + } + printf("DEBUG rank=%d nprocs=%d nsenders=%d check=%s\n", + my_rank, w_size, w_size - 1, _ok ? "OK" : "FAIL"); + } else { + printf("DEBUG rank=%d nprocs=%d target=%d check=OK\n", + my_rank, w_size, master_rank); + } + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/kpartners/kpartners_nb.c b/src/kpartners/kpartners_nb.c index b34fd921b..795b00975 100644 --- a/src/kpartners/kpartners_nb.c +++ b/src/kpartners/kpartners_nb.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); + install_shutdown_handler(); int npartners=1; /*number of random communication partners per rank (-k flag)*/ @@ -31,7 +31,7 @@ int main(int argc, char** argv){ } else { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } } @@ -40,7 +40,7 @@ int main(int argc, char** argv){ if(npartners<1){ if(my_rank==master_rank){ fprintf(stderr,"k (%d) must be >= 1\n",npartners); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } if(npartners>w_size-1){ @@ -51,23 +51,71 @@ int main(int argc, char** argv){ } /* - * Build npartners random permutations of [0, w_size-1]. - * For permutation p, rank i sends to perms[p*w_size + i]. - * Each rank appears as a target exactly once per permutation, - * so every rank sends npartners messages and receives npartners messages. - * Partners are fixed for the entire run (generated from rand_seed). + * Build npartners random permutations of [0, w_size-1] such that the + * resulting communication graph is k-regular AND every rank has k + * DISTINCT partners (no self-pairings, no duplicate target across + * permutations). For permutation p, rank i sends to perms[p*w_size + i]. + * + * Algorithm: rejection sampling. Each permutation must + * (a) have no fixed point (perms[p][i] != i — avoids self-send), and + * (b) for every i, perms[p][i] != perms[q][i] for all q < p (avoids + * column duplicates, i.e. same target as an earlier permutation). + * If a generated permutation fails, retry. For small k relative to + * w_size this terminates quickly. After many attempts fail we fall + * back to a swap-repair pass. Partners are fixed for the entire run. */ int *perms; perms=(int*)malloc_align(sizeof(int)*npartners*w_size); if(perms==NULL){ fprintf(stderr,"Failed to allocate permutation table on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } + int attempts; for(p=0;p= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -166,6 +216,48 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + /* Deterministic post-loop exchange: for each permutation p, invert + * it locally to discover which rank sends to me (since rejection + * sampling guarantees distinct senders per row, this is unique). + * Then receive from that specific source and verify the payload is + * the sender's rank value. Barrier first to drain any + * measurement-loop ANY_TAG receives so they don't consume our + * debug-tagged sends. */ + MPI_Barrier(MPI_COMM_WORLD); + int _p, _b, _ok = 1; + int *inverse = (int*)malloc_align(sizeof(int)*npartners*w_size); + if (inverse == NULL) { + fprintf(stderr,"Failed to allocate inverse perms on rank %d\n",my_rank); + MPI_Abort(MPI_COMM_WORLD, -1); + } + for (_p = 0; _p < npartners; _p++) { + int q; + for (q = 0; q < w_size; q++) inverse[_p*w_size + perms[_p*w_size + q]] = q; + } + MPI_Request *dbg_reqs = (MPI_Request*)malloc_align(sizeof(MPI_Request)*npartners*2); + for (_p = 0; _p < npartners; _p++) { + int from = inverse[_p*w_size + my_rank]; + int to = perms[_p*w_size + my_rank]; + MPI_Irecv(&recv_buf[_p*msg_size], msg_size, MPI_BYTE, from, 0xD0+_p, + MPI_COMM_WORLD, &dbg_reqs[_p]); + MPI_Isend(send_buf, msg_size, MPI_BYTE, to, 0xD0+_p, + MPI_COMM_WORLD, &dbg_reqs[npartners + _p]); + } + MPI_Waitall(npartners*2, dbg_reqs, MPI_STATUSES_IGNORE); + for (_p = 0; _p < npartners && _ok; _p++) { + int expected_from = inverse[_p*w_size + my_rank]; + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[(size_t)_p*msg_size + _b] != (unsigned char)expected_from) _ok = 0; + } + free(dbg_reqs); + free(inverse); + printf("DEBUG rank=%d nprocs=%d k=%d check=%s\n", + my_rank, w_size, npartners, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/pairwise/pairwise_b.c b/src/pairwise/pairwise_b.c index c8fca4f87..2465ea068 100644 --- a/src/pairwise/pairwise_b.c +++ b/src/pairwise/pairwise_b.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); + install_shutdown_handler(); char *comm_mode="offpair"; int target_offset=1; @@ -34,7 +34,7 @@ int main(int argc, char** argv){ } else { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } } @@ -46,13 +46,13 @@ int main(int argc, char** argv){ sched_setaffinity(0, sizeof(mask), &mask);*/ /*allocate buffers*/ - int send_buf_size, recv_buf_size; + size_t send_buf_size, recv_buf_size; unsigned char *send_buf; unsigned char *recv_buf; int *targets; send_buf_size=msg_size; - recv_buf_size=measure_granularity*msg_size; + recv_buf_size=(size_t)measure_granularity*msg_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -61,12 +61,12 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || targets==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } - /*fill send buffer with dummies*/ + /*fill send buffer: rank-as-payload under -debug so partners can verify*/ for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -142,6 +144,32 @@ int main(int argc, char** argv){ } }while(endless); + /* debug: deterministic post-loop exchange to verify partner identity and + * payload integrity. Send to targets[my_rank] and receive from this rank's + * inverse source (the rank whose target is me). These differ in the + * non-reciprocal modes (perm/rot), so a non-blocking exchange is used to + * avoid deadlock. Barrier first to drain any measurement-loop ANY_TAG + * receives so they don't consume our debug-tagged sends. */ + if (debug_mode) { + MPI_Barrier(MPI_COMM_WORLD); + int dst = targets[my_rank]; + int src = -1, _t; + for (_t = 0; _t < w_size; _t++) if (targets[_t] == my_rank) { src = _t; break; } + int _ok = 1, _b; + if (dst != my_rank && src >= 0) { + MPI_Request reqs[2]; + MPI_Irecv(recv_buf, msg_size, MPI_BYTE, src, 0xD, MPI_COMM_WORLD, &reqs[0]); + MPI_Isend(send_buf, msg_size, MPI_BYTE, dst, 0xD, MPI_COMM_WORLD, &reqs[1]); + MPI_Waitall(2, reqs, MPI_STATUSES_IGNORE); + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[_b] != (unsigned char)src) _ok = 0; + } + printf("DEBUG rank=%d nprocs=%d partner=%d check=%s\n", + my_rank, w_size, dst, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/pairwise/pairwise_bsnbr.c b/src/pairwise/pairwise_bsnbr.c index 0cb85e10d..23695d7cd 100644 --- a/src/pairwise/pairwise_bsnbr.c +++ b/src/pairwise/pairwise_bsnbr.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); char *comm_mode="offpair"; int target_offset=1; @@ -34,7 +34,7 @@ int main(int argc, char** argv){ } else { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } } @@ -46,14 +46,14 @@ int main(int argc, char** argv){ sched_setaffinity(0, sizeof(mask), &mask);*/ /*allocate buffers*/ - int send_buf_size, recv_buf_size; + size_t send_buf_size, recv_buf_size; unsigned char *send_buf; unsigned char *recv_buf; int *targets; MPI_Request *recv_requests; - + send_buf_size=msg_size; - recv_buf_size=measure_granularity*msg_size; + recv_buf_size=(size_t)measure_granularity*msg_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -63,12 +63,12 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || recv_requests==NULL || targets==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } - /*fill send buffer with dummies*/ + /*fill send buffer: rank-as-payload under -debug so partners can verify*/ for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -155,6 +157,32 @@ int main(int argc, char** argv){ } }while(endless); + /* debug: deterministic post-loop exchange to verify partner identity and + * payload integrity. Send to targets[my_rank] and receive from this rank's + * inverse source (the rank whose target is me). These differ in the + * non-reciprocal modes (perm/rot), so a non-blocking exchange is used to + * avoid deadlock. Barrier first to drain any measurement-loop ANY_TAG + * receives so they don't consume our debug-tagged sends. */ + if (debug_mode) { + MPI_Barrier(MPI_COMM_WORLD); + int dst = targets[my_rank]; + int src = -1, _t; + for (_t = 0; _t < w_size; _t++) if (targets[_t] == my_rank) { src = _t; break; } + int _ok = 1, _b; + if (dst != my_rank && src >= 0) { + MPI_Request reqs[2]; + MPI_Irecv(recv_buf, msg_size, MPI_BYTE, src, 0xD, MPI_COMM_WORLD, &reqs[0]); + MPI_Isend(send_buf, msg_size, MPI_BYTE, dst, 0xD, MPI_COMM_WORLD, &reqs[1]); + MPI_Waitall(2, reqs, MPI_STATUSES_IGNORE); + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[_b] != (unsigned char)src) _ok = 0; + } + printf("DEBUG rank=%d nprocs=%d partner=%d check=%s\n", + my_rank, w_size, dst, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/pairwise/pairwise_nb.c b/src/pairwise/pairwise_nb.c index 0e854c0ea..b9060bf60 100644 --- a/src/pairwise/pairwise_nb.c +++ b/src/pairwise/pairwise_nb.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); char *comm_mode="offpair"; int target_offset=1; @@ -34,7 +34,7 @@ int main(int argc, char** argv){ } else { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } } @@ -46,15 +46,15 @@ int main(int argc, char** argv){ sched_setaffinity(0, sizeof(mask), &mask);*/ /*allocate buffers*/ - int send_buf_size, recv_buf_size; + size_t send_buf_size, recv_buf_size; unsigned char *send_buf; unsigned char *recv_buf; int *targets; MPI_Request *recv_requests; MPI_Request *send_requests; - + send_buf_size=msg_size; - recv_buf_size=measure_granularity*msg_size; + recv_buf_size=(size_t)measure_granularity*msg_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -65,12 +65,12 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || recv_requests==NULL || targets==NULL || durations==NULL || send_requests==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } - /*fill send buffer with dummies*/ + /*fill send buffer: rank-as-payload under -debug so partners can verify*/ for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -149,6 +151,32 @@ int main(int argc, char** argv){ } }while(endless); + /* debug: deterministic post-loop exchange to verify partner identity and + * payload integrity. Send to targets[my_rank] and receive from this rank's + * inverse source (the rank whose target is me). These differ in the + * non-reciprocal modes (perm/rot), so a non-blocking exchange is used to + * avoid deadlock. Barrier first to drain any measurement-loop ANY_TAG + * receives so they don't consume our debug-tagged sends. */ + if (debug_mode) { + MPI_Barrier(MPI_COMM_WORLD); + int dst = targets[my_rank]; + int src = -1, _t; + for (_t = 0; _t < w_size; _t++) if (targets[_t] == my_rank) { src = _t; break; } + int _ok = 1, _b; + if (dst != my_rank && src >= 0) { + MPI_Request reqs[2]; + MPI_Irecv(recv_buf, msg_size, MPI_BYTE, src, 0xD, MPI_COMM_WORLD, &reqs[0]); + MPI_Isend(send_buf, msg_size, MPI_BYTE, dst, 0xD, MPI_COMM_WORLD, &reqs[1]); + MPI_Waitall(2, reqs, MPI_STATUSES_IGNORE); + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[_b] != (unsigned char)src) _ok = 0; + } + printf("DEBUG rank=%d nprocs=%d partner=%d check=%s\n", + my_rank, w_size, dst, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/pingpong/pingpong_b.c b/src/pingpong/pingpong_b.c index 2b2521219..a71e4e0e6 100644 --- a/src/pingpong/pingpong_b.c +++ b/src/pingpong/pingpong_b.c @@ -17,7 +17,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -25,7 +25,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -48,17 +48,17 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } if(w_size!=2){ fprintf(stderr,"Needs two processes to ping-pong. %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ for(i=0;i= warm_up_iters) record_duration((MPI_Wtime()-measure_start_time)/2.0); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -127,35 +128,21 @@ int main(int argc, char** argv){ } } }while(endless); - /*write results to file*/ - MPI_Barrier(MPI_COMM_WORLD); - - - // For pingpong we can avoid doing allgather etc from common.h (we just need to report rank 0 time) - if(my_rank==master_rank){ - int num_samples; - int start_index; - if (curr_iters - warm_up_iters > max_samples) - { - num_samples = max_samples; - start_index = curr_iters % max_samples; - } - else - { - num_samples = curr_iters - warm_up_iters; - start_index = warm_up_iters; - } - printf("Time,Bandwidth\n"); - for(i = 0; i < num_samples; i++){ - float time = durations[(start_index + i) % max_samples]/2; - float bandwidth = ((msg_size * 8.0) / 1000000000.0) / time; - printf("%.9f,%.9f\n", time, bandwidth); - } - printf("Ran %d iterations. Measured %d iterations.\n", curr_iters, num_samples); + if (debug_mode) { + unsigned char _expected = (my_rank == master_rank) + ? (unsigned char)receiver_rank : (unsigned char)master_rank; + int _b, _ok = 1; + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[_b] != _expected) _ok = 0; + printf("DEBUG rank=%d nprocs=%d check=%s\n", my_rank, w_size, _ok ? "OK" : "FAIL"); fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); } +done: + /*write results to file*/ + MPI_Barrier(MPI_COMM_WORLD); + write_results(); - /*free allocated buffers*/ free(durations); free(send_buf); diff --git a/src/pingpong/pingpong_pairwise_b.c b/src/pingpong/pingpong_pairwise_b.c index 682a3fa62..ce589d109 100644 --- a/src/pingpong/pingpong_pairwise_b.c +++ b/src/pingpong/pingpong_pairwise_b.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,14 +26,14 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } if(w_size%2==1){ if(my_rank==master_rank){ fprintf(stderr, "Benchmark needs an even number of ranks\n"); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -59,12 +59,12 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ for(i=0;i= warm_up_iters) record_duration((MPI_Wtime()-measure_start_time)/2.0); }else{ for(i=0;i= warm_up_iters) record_duration((MPI_Wtime()-measure_start_time)/2.0); }else{ for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -114,6 +116,17 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _e = w_size * (w_size - 1) / 2, _ok = 1, _b; + if (my_rank == master_rank) + for (_b = 0; _b < msg_size_ints && _ok; _b++) + if (recv_buf[_b] != _e) _ok = 0; + printf("DEBUG rank=%d nprocs=%d root=%d check=%s\n", + my_rank, w_size, master_rank, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/reduce/reduce_nb.c b/src/reduce/reduce_nb.c index 12df565d7..2d9504e18 100644 --- a/src/reduce/reduce_nb.c +++ b/src/reduce/reduce_nb.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -45,7 +45,7 @@ int main(int argc, char** argv){ if(msg_size%sizeof(int)!=0){ if(my_rank==master_rank){ fprintf(stderr, "Msg-size (%d) must be divisible by size of int (%ld)",msg_size,sizeof(int)); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -58,12 +58,12 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || durations==NULL || requests==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -117,6 +119,17 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _e = w_size * (w_size - 1) / 2, _ok = 1, _b; + if (my_rank == master_rank) + for (_b = 0; _b < msg_size_ints && _ok; _b++) + if (recv_buf[_b] != _e) _ok = 0; + printf("DEBUG rank=%d nprocs=%d root=%d check=%s\n", + my_rank, w_size, master_rank, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/reduce_scatter/reduce_scatter_b.c b/src/reduce_scatter/reduce_scatter_b.c index 2a35faa3e..ad61ac071 100644 --- a/src/reduce_scatter/reduce_scatter_b.c +++ b/src/reduce_scatter/reduce_scatter_b.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -45,26 +45,26 @@ int main(int argc, char** argv){ if(msg_size%sizeof(int)!=0){ if(my_rank==master_rank){ fprintf(stderr, "Msg-size (%d) must be divisible by size of int (%ld)",msg_size,sizeof(int)); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } msg_size_ints=msg_size/sizeof(int); /*each rank sends w_size*msg_size total, receives msg_size*/ - send_buf=(int*)malloc_align(w_size*msg_size); + send_buf=(int*)malloc_align((size_t)w_size*msg_size); recv_buf=(int*)malloc_align(msg_size); recvcounts=(int*)malloc_align(sizeof(int)*w_size); durations=(double *)malloc_align(sizeof(double)*max_samples); if(send_buf==NULL || recv_buf==NULL || recvcounts==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ - for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -122,6 +124,15 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _e = w_size * (w_size - 1) / 2, _ok = 1, _b; + for (_b = 0; _b < msg_size_ints && _ok; _b++) + if (recv_buf[_b] != _e) _ok = 0; + printf("DEBUG rank=%d nprocs=%d check=%s\n", my_rank, w_size, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/reduce_scatter/reduce_scatter_nb.c b/src/reduce_scatter/reduce_scatter_nb.c index 0d68ef93e..85b8b8fc8 100644 --- a/src/reduce_scatter/reduce_scatter_nb.c +++ b/src/reduce_scatter/reduce_scatter_nb.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -46,14 +46,14 @@ int main(int argc, char** argv){ if(msg_size%sizeof(int)!=0){ if(my_rank==master_rank){ fprintf(stderr, "Msg-size (%d) must be divisible by size of int (%ld)",msg_size,sizeof(int)); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } msg_size_ints=msg_size/sizeof(int); /*each rank sends w_size*msg_size total, receives msg_size*/ - send_buf=(int*)malloc_align(w_size*msg_size); + send_buf=(int*)malloc_align((size_t)w_size*msg_size); recv_buf=(int*)malloc_align(msg_size); recvcounts=(int*)malloc_align(sizeof(int)*w_size); durations=(double *)malloc_align(sizeof(double)*max_samples); @@ -61,12 +61,12 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || recvcounts==NULL || durations==NULL || requests==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ - for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -125,6 +127,15 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _e = w_size * (w_size - 1) / 2, _ok = 1, _b; + for (_b = 0; _b < msg_size_ints && _ok; _b++) + if (recv_buf[_b] != _e) _ok = 0; + printf("DEBUG rank=%d nprocs=%d check=%s\n", my_rank, w_size, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/ring/ring_bsnbr.c b/src/ring/ring_bsnbr.c index 8618e5883..bbff81c54 100644 --- a/src/ring/ring_bsnbr.c +++ b/src/ring/ring_bsnbr.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); bool rand_ring=false; @@ -31,7 +31,7 @@ int main(int argc, char** argv){ } else { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } } @@ -43,14 +43,14 @@ int main(int argc, char** argv){ sched_setaffinity(0, sizeof(mask), &mask);*/ /*allocate buffers*/ - int send_buf_size, recv_buf_size; + size_t send_buf_size, recv_buf_size; unsigned char *send_buf; unsigned char *recv_buf; int *targets; MPI_Request *recv_requests; - + send_buf_size=msg_size; - recv_buf_size=2*measure_granularity*msg_size; + recv_buf_size=(size_t)2*measure_granularity*msg_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -60,12 +60,12 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || recv_requests==NULL || targets==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } - /*fill send buffer with dummies*/ + /*fill send buffer: rank-as-payload under -debug so neighbours can verify*/ for(i=0;i rank" map; build the inverse so each + * rank can find its position in the (possibly permuted) ring. Then the + * left/right neighbours are the ranks at adjacent positions, which + * guarantees a single Hamiltonian cycle in both directions and full + * reciprocity (A.left = B <=> B.right = A). */ + int *positions = (int*)malloc_align(sizeof(int)*w_size); + if (positions == NULL) { + fprintf(stderr, "Failed to allocate positions on rank %d\n", my_rank); + MPI_Abort(MPI_COMM_WORLD, -1); + } + for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -138,12 +152,40 @@ int main(int argc, char** argv){ } }while(endless); + /* debug: deterministic post-loop exchange to verify both ring neighbours + * sent the expected payload (their rank). Uses specific sources so a + * broken topology (e.g. the old -rring bug) is detected. Barrier + * first to drain any measurement-loop ANY_TAG receives. */ + if (debug_mode) { + MPI_Barrier(MPI_COMM_WORLD); + /* reuse the already-allocated recv_buf (>= 2*msg_size) instead of a + * stack VLA, which would risk a stack overflow for large msg_size */ + unsigned char *left_buf = recv_buf; + unsigned char *right_buf = recv_buf + msg_size; + MPI_Request reqs[2]; + int _ok = 1, _b; + MPI_Irecv(left_buf, msg_size, MPI_BYTE, left_neighbor, 0xD, MPI_COMM_WORLD, &reqs[0]); + MPI_Irecv(right_buf, msg_size, MPI_BYTE, right_neighbor, 0xD, MPI_COMM_WORLD, &reqs[1]); + MPI_Send(send_buf, msg_size, MPI_BYTE, left_neighbor, 0xD, MPI_COMM_WORLD); + MPI_Send(send_buf, msg_size, MPI_BYTE, right_neighbor, 0xD, MPI_COMM_WORLD); + MPI_Waitall(2, reqs, MPI_STATUSES_IGNORE); + for (_b = 0; _b < msg_size && _ok; _b++) + if (left_buf[_b] != (unsigned char)left_neighbor) _ok = 0; + for (_b = 0; _b < msg_size && _ok; _b++) + if (right_buf[_b] != (unsigned char)right_neighbor) _ok = 0; + printf("DEBUG rank=%d nprocs=%d left=%d right=%d check=%s\n", + my_rank, w_size, left_neighbor, right_neighbor, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); /*free allocated buffers*/ free(targets); + free(positions); free(durations); free(send_buf); free(recv_buf); diff --git a/src/ring/ring_nb.c b/src/ring/ring_nb.c index db2a13782..675820f0e 100644 --- a/src/ring/ring_nb.c +++ b/src/ring/ring_nb.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); bool rand_ring=false; @@ -31,7 +31,7 @@ int main(int argc, char** argv){ } else { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } } @@ -43,15 +43,15 @@ int main(int argc, char** argv){ sched_setaffinity(0, sizeof(mask), &mask);*/ /*allocate buffers*/ - int send_buf_size, recv_buf_size; + size_t send_buf_size, recv_buf_size; unsigned char *send_buf; unsigned char *recv_buf; int *targets; MPI_Request *recv_requests; MPI_Request *send_requests; - + send_buf_size=msg_size; - recv_buf_size=2*measure_granularity*msg_size; + recv_buf_size=(size_t)2*measure_granularity*msg_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -62,12 +62,12 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || recv_requests==NULL || targets==NULL || durations==NULL || send_requests==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } - /*fill send buffer with dummies*/ + /*fill send buffer: rank-as-payload under -debug so neighbours can verify*/ for(i=0;i rank" map; build the inverse so each + * rank can find its position in the (possibly permuted) ring. Then the + * left/right neighbours are the ranks at adjacent positions, which + * guarantees a single Hamiltonian cycle in both directions and full + * reciprocity (A.left = B <=> B.right = A). */ + int *positions = (int*)malloc_align(sizeof(int)*w_size); + if (positions == NULL) { + fprintf(stderr, "Failed to allocate positions on rank %d\n", my_rank); + MPI_Abort(MPI_COMM_WORLD, -1); + } + for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -142,12 +157,40 @@ int main(int argc, char** argv){ } }while(endless); + /* debug: deterministic post-loop exchange to verify both ring neighbours + * sent the expected payload (their rank). Uses specific sources so a + * broken topology (e.g. the old -rring bug) is detected. Barrier + * first to drain any measurement-loop ANY_TAG receives. */ + if (debug_mode) { + MPI_Barrier(MPI_COMM_WORLD); + /* reuse the already-allocated recv_buf (>= 2*msg_size) instead of a + * stack VLA, which would risk a stack overflow for large msg_size */ + unsigned char *left_buf = recv_buf; + unsigned char *right_buf = recv_buf + msg_size; + MPI_Request reqs[4]; + int _ok = 1, _b; + MPI_Irecv(left_buf, msg_size, MPI_BYTE, left_neighbor, 0xD, MPI_COMM_WORLD, &reqs[0]); + MPI_Irecv(right_buf, msg_size, MPI_BYTE, right_neighbor, 0xD, MPI_COMM_WORLD, &reqs[1]); + MPI_Isend(send_buf, msg_size, MPI_BYTE, left_neighbor, 0xD, MPI_COMM_WORLD, &reqs[2]); + MPI_Isend(send_buf, msg_size, MPI_BYTE, right_neighbor, 0xD, MPI_COMM_WORLD, &reqs[3]); + MPI_Waitall(4, reqs, MPI_STATUSES_IGNORE); + for (_b = 0; _b < msg_size && _ok; _b++) + if (left_buf[_b] != (unsigned char)left_neighbor) _ok = 0; + for (_b = 0; _b < msg_size && _ok; _b++) + if (right_buf[_b] != (unsigned char)right_neighbor) _ok = 0; + printf("DEBUG rank=%d nprocs=%d left=%d right=%d check=%s\n", + my_rank, w_size, left_neighbor, right_neighbor, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); /*free allocated buffers*/ free(targets); + free(positions); free(durations); free(send_buf); free(recv_buf); diff --git a/src/scatter/scatter_b.c b/src/scatter/scatter_b.c index 8d53cad79..e2be0bcfe 100644 --- a/src/scatter/scatter_b.c +++ b/src/scatter/scatter_b.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -42,13 +42,13 @@ int main(int argc, char** argv){ unsigned char *recv_buf; if(my_rank==master_rank){ - send_buf=(unsigned char*)malloc_align(w_size*msg_size); + send_buf=(unsigned char*)malloc_align((size_t)w_size*msg_size); if(send_buf==NULL){ fprintf(stderr,"Failed to allocate send_buf on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } - for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -110,6 +112,16 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _b, _ok = 1; + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[_b] != (unsigned char)my_rank) _ok = 0; + printf("DEBUG rank=%d nprocs=%d root=%d check=%s\n", + my_rank, w_size, master_rank, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/scatter/scatter_nb.c b/src/scatter/scatter_nb.c index 8d6110318..e7a0c7068 100644 --- a/src/scatter/scatter_nb.c +++ b/src/scatter/scatter_nb.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,7 +26,7 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } @@ -43,13 +43,13 @@ int main(int argc, char** argv){ MPI_Request *requests; if(my_rank==master_rank){ - send_buf=(unsigned char*)malloc_align(w_size*msg_size); + send_buf=(unsigned char*)malloc_align((size_t)w_size*msg_size); if(send_buf==NULL){ fprintf(stderr,"Failed to allocate send_buf on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } - for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -113,6 +115,16 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _b, _ok = 1; + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[_b] != (unsigned char)my_rank) _ok = 0; + printf("DEBUG rank=%d nprocs=%d root=%d check=%s\n", + my_rank, w_size, master_rank, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/stencil/stencil_2d_nb.c b/src/stencil/stencil_2d_nb.c index 1d087af35..820330aa3 100644 --- a/src/stencil/stencil_2d_nb.c +++ b/src/stencil/stencil_2d_nb.c @@ -18,7 +18,7 @@ int main(int argc, char** argv){ MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); /*register signal handler*/ - signal(SIGUSR1,sig_handler); + install_shutdown_handler(); int dimx=0; /*0 means auto-factor via MPI_Dims_create*/ bool periodic=false; @@ -34,7 +34,7 @@ int main(int argc, char** argv){ } else { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } } @@ -47,7 +47,7 @@ int main(int argc, char** argv){ if(w_size%dimx!=0){ if(my_rank==master_rank){ fprintf(stderr,"dimx (%d) does not divide w_size (%d)\n",dimx,w_size); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } } dims[0]=dimx; @@ -81,14 +81,14 @@ int main(int argc, char** argv){ /*allocate buffers*/ /*4 directions, each of size msg_size; granularity batches them*/ - int send_buf_size, recv_buf_size; + size_t send_buf_size, recv_buf_size; unsigned char *send_buf; unsigned char *recv_buf; MPI_Request *send_requests; MPI_Request *recv_requests; send_buf_size=msg_size; - recv_buf_size=4*measure_granularity*msg_size; + recv_buf_size=(size_t)4*measure_granularity*msg_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -98,12 +98,12 @@ int main(int argc, char** argv){ if(send_buf==NULL || recv_buf==NULL || durations==NULL || send_requests==NULL || recv_requests==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } /*fill send buffer with dummies*/ for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); curr_iters++; if(burst_length!=0){ /*bcast needed for synch if bursts timed*/ if(my_rank==master_rank){ /*master decides if burst should be continued*/ @@ -168,6 +170,20 @@ int main(int argc, char** argv){ } }while(endless); + if (debug_mode) { + int _d, _b, _ok = 1; + const int _dirs[4] = {north, south, west, east}; + for (_d = 0; _d < 4 && _ok; _d++) { + if (_dirs[_d] < 0) continue; /* MPI_PROC_NULL — no neighbour, no data */ + for (_b = 0; _b < msg_size && _ok; _b++) + if (recv_buf[(size_t)_d * msg_size + _b] != (unsigned char)_dirs[_d]) _ok = 0; + } + printf("DEBUG rank=%d nprocs=%d north=%d south=%d west=%d east=%d check=%s\n", + my_rank, w_size, north, south, west, east, _ok ? "OK" : "FAIL"); + fflush(stdout); + MPI_Barrier(MPI_COMM_WORLD); + } +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); diff --git a/src/tools/checker.c b/src/tools/checker.c index 3b2c718c1..95284755e 100644 --- a/src/tools/checker.c +++ b/src/tools/checker.c @@ -16,9 +16,9 @@ int main(int argc, char** argv){ MPI_Init(&argc,&argv); MPI_Comm_size(MPI_COMM_WORLD, &w_size); MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); - + /*register signal handler*/ - signal(SIGUSR1,sig_handler); //or SIGUSR1 here + install_shutdown_handler(); /*parse command line*/ int i, k; @@ -26,32 +26,32 @@ int main(int argc, char** argv){ for (i = 1; i < argc; i++) { if (my_rank == master_rank) { fprintf(stderr, "Unknown argument: %s\n", argv[i]); - exit(-1); } + MPI_Abort(MPI_COMM_WORLD, -1); } /*allocate buffers*/ - int send_buf_size, recv_buf_size; + size_t send_buf_size, recv_buf_size; unsigned char *send_buf; unsigned char *recv_buf; - send_buf_size=msg_size*w_size; - recv_buf_size=msg_size*w_size; - + send_buf_size=(size_t)msg_size*w_size; + recv_buf_size=(size_t)msg_size*w_size; + send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); durations=(double *)malloc_align(sizeof(double)*max_samples); - + if(send_buf==NULL || recv_buf==NULL || durations==NULL){ fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - exit(-1); + MPI_Abort(MPI_COMM_WORLD, -1); } - + /*fill send buffer with dummies*/ - for(i=0;itm_hour - , time_str_tm->tm_min - , time_str_tm->tm_sec - , time_now.tv_usec); - fflush(fd_temp); + time_t t = time(NULL); + struct tm *tm = localtime(&t); + char s[64]; + strftime(s, sizeof(s), "checker_%Y%m%d_%H%M%S.log", tm); + fd_temp=fopen(s,"w"); + if(fd_temp==NULL){ + fprintf(stderr,"Failed to open log file %s on rank %d\n",s,my_rank); + MPI_Abort(MPI_COMM_WORLD, -1); + } + struct timeval time_now; + gettimeofday(&time_now, NULL); + struct tm *time_str_tm; + time_str_tm = gmtime(&time_now.tv_sec); + if(endless){ + fprintf(fd_temp, "endless, %i B, %i iter, %i grty\n",msg_size,max_iters,measure_granularity); + }else{ + fprintf(fd_temp, "limited, %i B, %i iter, %i grty\n",msg_size,max_iters,measure_granularity); + } + fprintf(fd_temp, "%02i:%02i:%02i:%06li\n---------------\n" + , time_str_tm->tm_hour + , time_str_tm->tm_min + , time_str_tm->tm_sec + , time_now.tv_usec); + fflush(fd_temp); } //----------------- MPI_Barrier(MPI_COMM_WORLD); do{ for(k=0;k= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); /*write result to ring buffer*/ curr_iters++; //----------------- - if(my_rank==master_rank){ + if(my_rank==master_rank){ struct timeval time_now; gettimeofday(&time_now, NULL); struct tm *time_str_tm; time_str_tm = gmtime(&time_now.tv_sec); - fprintf(fd_temp, "%02i:%02i:%02i:%06li | %i | %i\n" + fprintf(fd_temp, "%02i:%02i:%02i:%06li | %i | %i\n" , time_str_tm->tm_hour , time_str_tm->tm_min , time_str_tm->tm_sec @@ -147,20 +150,20 @@ int main(int argc, char** argv){ } }while(endless); +done: /*write results to file*/ MPI_Barrier(MPI_COMM_WORLD); write_results(); - + //----------------- - fclose(fd_temp); + if(my_rank==master_rank && fd_temp!=NULL) fclose(fd_temp); //----------------- - + /*free allocated buffers*/ free(durations); free(send_buf); free(recv_buf); - + /*exit MPI library*/ MPI_Finalize(); } - diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..556c4e9ee --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,33 @@ +# ── Black-box benchmark test ────────────────────────────────────────────────── +# A regular single-process GTest that spawns mpirun -debug via +# popen() (see popen_helpers.h) and parses the structured debug output. The +# benchmark paths and mpiexec command are baked in at configure time +# (overridable via env vars). +# +# `nprocs` arg is forwarded to ctest's PROCESSORS property so `ctest -j` +# accounts for each suite's actual MPI process count and avoids +# oversubscribing the host. +function(add_benchmark_test name nprocs) + add_executable(${name} ${name}.cpp) + target_include_directories(${name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_compile_definitions(${name} PRIVATE + "BLINK_BIN_DIR=\"${CMAKE_BINARY_DIR}/bin\"" + "BLINK_MPIEXEC=\"${MPIEXEC_EXECUTABLE}\"" + "BLINK_NPROC_FLAG=\"${MPIEXEC_NUMPROC_FLAG}\"") + target_link_libraries(${name} PRIVATE GTest::GTest GTest::Main) + set_target_properties(${name} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + add_test(NAME ${name} COMMAND ${name}) + set_tests_properties(${name} PROPERTIES PROCESSORS ${nprocs}) +endfunction() + +# ── Registered tests ────────────────────────────────────────────────────────── +# nprocs is the MAXIMUM rank count any single test_*.cpp launches via mpirun. +add_benchmark_test(test_collectives 8) +add_benchmark_test(test_pairwise 8) +add_benchmark_test(test_ring 8) +add_benchmark_test(test_incast 8) +add_benchmark_test(test_pingpong 8) +add_benchmark_test(test_kpartners 8) +add_benchmark_test(test_stencil 8) +add_benchmark_test(test_misc 8) diff --git a/tests/popen_helpers.h b/tests/popen_helpers.h new file mode 100644 index 000000000..2b77a3c4b --- /dev/null +++ b/tests/popen_helpers.h @@ -0,0 +1,222 @@ +#pragma once +/* + * popen_helpers.h — shared helpers for spawning a benchmark under mpirun + * and parsing structured DEBUG output. + * + * Used by every test_*.cpp. Provides: + * - run_debug_capture(): launches the benchmark, returns parsed DEBUG + * fields keyed by rank, plus exit-status decoded via WIFEXITED/WEXITSTATUS. + * stderr is redirected into a temp file and surfaced on failure so the + * diagnostic is not lost. + * + * Configure-time defaults are baked in by CMake via -DBLINK_MPIEXEC=... + * etc. Each can be overridden at run time via environment variables + * (BLINK_MPIEXEC, BLINK_NPROC_FLAG, BLINK_BIN_DIR). + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace blink { + +inline std::string env_or(const char *key, const char *def) +{ + const char *v = std::getenv(key); + return v ? v : def; +} + +inline std::string mpiexec() { return env_or("BLINK_MPIEXEC", BLINK_MPIEXEC); } +inline std::string nproc_flag() { return env_or("BLINK_NPROC_FLAG", BLINK_NPROC_FLAG); } +inline std::string bin_dir() { return env_or("BLINK_BIN_DIR", BLINK_BIN_DIR); } + +/* A single parsed DEBUG line: rank => key/value map. */ +using DebugFields = std::map; +using DebugMap = std::map; + +/* Result of a debug run. */ +struct DebugRun { + DebugMap ranks; /* parsed DEBUG lines, keyed by rank */ + int exit_code; /* WEXITSTATUS, or -signum if killed */ + bool normal_exit; /* true iff WIFEXITED */ + std::string stdout_raw; /* full stdout */ + std::string stderr_raw; /* full stderr (drained from temp file) */ + std::string command; /* the command line that was run */ +}; + +/* Parse a single DEBUG line into key/value fields. Returns rank, or -1 + * if the line does not begin with "DEBUG rank=". */ +inline int parse_debug_line(const std::string& line, DebugFields& out) +{ + auto p = line.find("DEBUG rank="); + if (p == std::string::npos) return -1; + p += strlen("DEBUG rank="); + int rank = 0; + size_t consumed = 0; + try { rank = std::stoi(line.substr(p), &consumed); } + catch (...) { return -1; } + out["rank"] = std::to_string(rank); + p += consumed; + /* parse key=val tokens separated by whitespace */ + while (p < line.size()) { + while (p < line.size() && isspace((unsigned char)line[p])) p++; + size_t eq = line.find('=', p); + if (eq == std::string::npos) break; + std::string k = line.substr(p, eq - p); + size_t v_start = eq + 1; + size_t v_end = v_start; + while (v_end < line.size() && !isspace((unsigned char)line[v_end])) v_end++; + std::string v = line.substr(v_start, v_end - v_start); + out[k] = v; + p = v_end; + } + return rank; +} + +/* Spawn the benchmark, capture stdout + stderr, parse DEBUG lines, decode + * the wait status, and return a DebugRun. This does NOT assert anything — + * callers should check `run.normal_exit && run.exit_code == 0` themselves + * and use gtest macros (so context strings can be tailored per call). */ +inline DebugRun run_debug_capture(const std::string& binary, + int nprocs, + const std::string& extra = "") +{ + DebugRun r; + char errfile_template[] = "/tmp/blink_test_err_XXXXXX"; + int fd = mkstemp(errfile_template); + std::string errfile = (fd >= 0) ? errfile_template : "/dev/null"; + if (fd >= 0) close(fd); + + r.command = mpiexec() + " " + nproc_flag() + " " + + std::to_string(nprocs) + " " + + bin_dir() + "/" + binary + + " -debug -iter 1 -warmup 0 " + extra + + " 2>" + errfile; + + FILE *pipe = popen(r.command.c_str(), "r"); + if (!pipe) { + r.exit_code = -1; + r.normal_exit = false; + return r; + } + + char line[1024]; + while (fgets(line, sizeof(line), pipe)) { + r.stdout_raw += line; + DebugFields fields; + int rank = parse_debug_line(line, fields); + if (rank >= 0) r.ranks[rank] = std::move(fields); + } + int status = pclose(pipe); + if (status == -1) { + r.exit_code = -1; + r.normal_exit = false; + } else if (WIFEXITED(status)) { + r.exit_code = WEXITSTATUS(status); + r.normal_exit = true; + } else if (WIFSIGNALED(status)) { + r.exit_code = -WTERMSIG(status); + r.normal_exit = false; + } else { + r.exit_code = status; + r.normal_exit = false; + } + + /* drain stderr from the temp file */ + if (errfile != "/dev/null") { + FILE *ef = fopen(errfile.c_str(), "r"); + if (ef) { + char buf[1024]; + size_t n; + while ((n = fread(buf, 1, sizeof(buf), ef)) > 0) + r.stderr_raw.append(buf, n); + fclose(ef); + } + unlink(errfile.c_str()); + } + return r; +} + +/* Convenience: assert the benchmark ran successfully and return its parsed + * DEBUG lines. On failure, dump captured stderr / stdout to gtest output. */ +inline DebugMap run_debug(const std::string& binary, + int nprocs, + const std::string& extra = "") +{ + DebugRun r = run_debug_capture(binary, nprocs, extra); + EXPECT_TRUE(r.normal_exit) + << "command: " << r.command << "\n" + << "exit_code: " << r.exit_code << "\n" + << "stderr:\n" << r.stderr_raw << "\n" + << "stdout:\n" << r.stdout_raw; + EXPECT_EQ(r.exit_code, 0) + << "command: " << r.command << "\n" + << "stderr:\n" << r.stderr_raw << "\n" + << "stdout:\n" << r.stdout_raw; + return r.ranks; +} + +/* Look up an integer field for the given rank. Returns -1 if absent. */ +inline int get_int(const DebugMap& m, int rank, const std::string& key, int def = -1) +{ + auto rit = m.find(rank); + if (rit == m.end()) return def; + auto fit = rit->second.find(key); + if (fit == rit->second.end()) return def; + try { return std::stoi(fit->second); } + catch (...) { return def; } +} + +/* Look up a string field for the given rank. Returns "" if absent. */ +inline std::string get_str(const DebugMap& m, int rank, const std::string& key) +{ + auto rit = m.find(rank); + if (rit == m.end()) return ""; + auto fit = rit->second.find(key); + if (fit == rit->second.end()) return ""; + return fit->second; +} + +/* Assert that every rank emitted a check= field AND reported check=OK. + * Requiring the field to be present (rather than silently skipping ranks that + * omit it) makes data-integrity tests fail loudly if a benchmark ever stops + * emitting its self-check, instead of passing vacuously. */ +inline void check_all_ok(const DebugMap& m, const std::string& ctx) +{ + for (const auto& [r, fields] : m) { + auto it = fields.find("check"); + EXPECT_NE(it, fields.end()) + << ctx << ": rank " << r << " emitted no check= field"; + if (it == fields.end()) continue; + EXPECT_EQ(it->second, "OK") + << ctx << ": rank " << r << " check=" << it->second; + } +} + +/* Assert that ranks 0..n-1 are all present in the map. */ +inline void check_coverage(const DebugMap& m, int n, const std::string& ctx) +{ + ASSERT_EQ((int)m.size(), n) + << ctx << ": expected " << n << " DEBUG lines, got " << m.size(); + for (int r = 0; r < n; r++) + EXPECT_TRUE(m.count(r)) << ctx << ": rank " << r << " missing"; +} + +/* Assert that every rank that emits nprocs= reports the expected value. */ +inline void check_nprocs(const DebugMap& m, int expected, const std::string& ctx) +{ + for (const auto& [r, fields] : m) { + auto it = fields.find("nprocs"); + if (it == fields.end()) continue; + EXPECT_EQ(std::stoi(it->second), expected) + << ctx << ": rank " << r << " nprocs=" << it->second + << " expected " << expected; + } +} + +} // namespace blink diff --git a/tests/test_collectives.cpp b/tests/test_collectives.cpp new file mode 100644 index 000000000..10f4c79f6 --- /dev/null +++ b/tests/test_collectives.cpp @@ -0,0 +1,186 @@ +/* + * test_collectives.cpp — correctness tests for collective benchmarks. + * + * Strategy: spawn the actual benchmark binary with -debug and parse + * "DEBUG rank=X nprocs=Y [root=Z] check=OK|FAIL" lines. + * No MPI in this binary. + * + * Properties checked: + * Coverage — every rank 0..N-1 appears exactly once. + * Nprocs — every rank reports the correct communicator size. + * Root — rooted collectives use the expected master_rank. + * NonDefaultRoot — -mrank flag correctly changes the root. + * DataIntegrity — every rank reports check=OK (data-integrity verified + * by the benchmark itself in debug mode). + * Agreement — _nb variant reports the same result as the _b variant. + * + * Data filled in debug mode (inside the benchmark, not here): + * allgather/alltoall fill send chunk with my_rank byte; verify recv[r*M+j]==r + * allreduce/reduce fill send with my_rank (int); verify SUM == N*(N-1)/2 + * reduce_scatter same; scatter distributes one block to each rank + * broadcast root fills with master_rank byte; all verify + * gather fill send with my_rank byte; root verifies recv[r*M+j]==r + * scatter root prepares chunk r=byte(r); each rank verifies recv[j]==my_rank + * barrier reaches the debug print ⟹ barrier completed (deadlock check) + * + * Requires 8 MPI ranks. + */ +#include +#include +#include "popen_helpers.h" + +using blink::DebugMap; +using blink::run_debug; +using blink::get_int; +using blink::get_str; +using blink::check_all_ok; +using blink::check_coverage; +using blink::check_nprocs; + +/* ── shared helpers ─────────────────────────────────────────────────────────── */ + +static void check_root(const DebugMap& m, int expected_root, const std::string& ctx) +{ + for (const auto& [r, fields] : m) { + auto it = fields.find("root"); + if (it == fields.end()) continue; + EXPECT_EQ(std::stoi(it->second), expected_root) + << ctx << ": rank " << r << " reports root=" << it->second; + } +} + +/* ── unrooted collectives ───────────────────────────────────────────────────── */ + +class UnrootedCollectiveTest : public ::testing::TestWithParam { +protected: + static constexpr int N = 8; +}; + +TEST_P(UnrootedCollectiveTest, Coverage) { + auto m = run_debug(GetParam(), N); + check_coverage(m, N, GetParam()); +} +TEST_P(UnrootedCollectiveTest, Nprocs) { + auto m = run_debug(GetParam(), N); + check_nprocs(m, N, GetParam()); +} +TEST_P(UnrootedCollectiveTest, DataIntegrity) { + auto m = run_debug(GetParam(), N); + check_coverage(m, N, GetParam()); + check_all_ok(m, GetParam()); +} +TEST_P(UnrootedCollectiveTest, DataIntegrity_burst) { + auto m = run_debug(GetParam(), N, "-blength 0.001"); + check_coverage(m, N, GetParam() + " burst"); + check_all_ok(m, GetParam() + " burst"); +} + +INSTANTIATE_TEST_SUITE_P( + Collectives, UnrootedCollectiveTest, + ::testing::Values( + "allgather_b", "allgather_nb", "allgather_comm_only", + "allreduce_b", "allreduce_nb", + "alltoall_b", "alltoall_nb", "alltoall_man", "alltoall_comm_only", + "barrier_b", "barrier_nb", + "reduce_scatter_b", "reduce_scatter_nb" + ) +); + +/* ── rooted collectives ─────────────────────────────────────────────────────── */ + +class RootedCollectiveTest : public ::testing::TestWithParam { +protected: + static constexpr int N = 8; + static constexpr int MASTER = 0; + static constexpr int ALT = 3; +}; + +TEST_P(RootedCollectiveTest, Coverage) { + auto m = run_debug(GetParam(), N); + check_coverage(m, N, GetParam()); +} +TEST_P(RootedCollectiveTest, Nprocs) { + auto m = run_debug(GetParam(), N); + check_nprocs(m, N, GetParam()); +} +TEST_P(RootedCollectiveTest, DefaultRoot) { + auto m = run_debug(GetParam(), N); + check_root(m, MASTER, GetParam() + " default root"); +} +TEST_P(RootedCollectiveTest, DataIntegrity) { + auto m = run_debug(GetParam(), N); + check_coverage(m, N, GetParam()); + check_all_ok(m, GetParam()); +} +TEST_P(RootedCollectiveTest, DataIntegrity_burst) { + auto m = run_debug(GetParam(), N, "-blength 0.001"); + check_coverage(m, N, GetParam() + " burst"); + check_all_ok(m, GetParam() + " burst"); +} +TEST_P(RootedCollectiveTest, NonDefaultRoot) { + auto m = run_debug(GetParam(), N, "-mrank " + std::to_string(ALT)); + check_coverage(m, N, GetParam() + " mrank=3"); + check_nprocs(m, N, GetParam() + " mrank=3"); + check_root(m, ALT, GetParam() + " mrank=3"); + check_all_ok(m, GetParam() + " mrank=3"); +} + +INSTANTIATE_TEST_SUITE_P( + Collectives, RootedCollectiveTest, + ::testing::Values( + "broadcast_b", "broadcast_nb", + "gather_b", "gather_nb", + "scatter_b", "scatter_nb", + "reduce_b", "reduce_nb" + ) +); + +/* ── nb variants agree with b variants ─────────────────────────────────────── */ + +class CollectiveAgreementTest : public ::testing::Test { +protected: + static constexpr int N = 8; + + static void check_agreement(const std::string& b_bin, const std::string& nb_bin) { + auto b = run_debug(b_bin, N); + auto nb = run_debug(nb_bin, N); + for (const auto& [r, fields_b] : b) { + if (!nb.count(r)) continue; + EXPECT_EQ(get_int(nb, r, "nprocs"), get_int(b, r, "nprocs")) + << "rank " << r << " nprocs differs: " << b_bin << " vs " << nb_bin; + EXPECT_EQ(get_int(nb, r, "root"), get_int(b, r, "root")) + << "rank " << r << " root differs: " << b_bin << " vs " << nb_bin; + EXPECT_EQ(get_str(nb, r, "check"), get_str(b, r, "check")) + << "rank " << r << " check differs: " << b_bin << " vs " << nb_bin; + } + } +}; + +TEST_F(CollectiveAgreementTest, Allgather) { check_agreement("allgather_b", "allgather_nb"); } +TEST_F(CollectiveAgreementTest, Allreduce) { check_agreement("allreduce_b", "allreduce_nb"); } +TEST_F(CollectiveAgreementTest, Alltoall) { check_agreement("alltoall_b", "alltoall_nb"); } +TEST_F(CollectiveAgreementTest, Barrier) { check_agreement("barrier_b", "barrier_nb"); } +TEST_F(CollectiveAgreementTest, Broadcast) { check_agreement("broadcast_b", "broadcast_nb"); } +TEST_F(CollectiveAgreementTest, Gather) { check_agreement("gather_b", "gather_nb"); } +TEST_F(CollectiveAgreementTest, Scatter) { check_agreement("scatter_b", "scatter_nb"); } +TEST_F(CollectiveAgreementTest, Reduce) { check_agreement("reduce_b", "reduce_nb"); } +TEST_F(CollectiveAgreementTest, ReduceScatter) { check_agreement("reduce_scatter_b", "reduce_scatter_nb"); } + +/* ── transfer-volume agreement ─────────────────────────────────────────────── */ +/* For a given -msgsize, every allgather variant must move the SAME per-rank + * payload. Regression guard: allgather_comm_only previously treated -msgsize as + * the total gathered size (msg_size/w_size per rank) while allgather_b/nb treat + * it as the per-rank contribution, making their numbers non-comparable. The + * benchmarks now emit bytes= under -debug. */ +TEST_F(CollectiveAgreementTest, AllgatherTransferVolume) { + const int M = 2048; /* multiple of sizeof(int) for the comm_only variant */ + const std::string sz = "-msgsize " + std::to_string(M); + auto b = run_debug("allgather_b", N, sz); + auto nb = run_debug("allgather_nb", N, sz); + auto co = run_debug("allgather_comm_only", N, sz); + for (int r = 0; r < N; r++) { + EXPECT_EQ(get_int(b, r, "bytes"), M) << "allgather_b rank " << r; + EXPECT_EQ(get_int(nb, r, "bytes"), M) << "allgather_nb rank " << r; + EXPECT_EQ(get_int(co, r, "bytes"), M) << "allgather_comm_only rank " << r; + } +} diff --git a/tests/test_incast.cpp b/tests/test_incast.cpp new file mode 100644 index 000000000..362f2139b --- /dev/null +++ b/tests/test_incast.cpp @@ -0,0 +1,275 @@ +/* + * test_incast.cpp — correctness tests for incast benchmarks. + * + * Strategy: spawn the actual benchmark binary with -debug and parse: + * "DEBUG rank=X nprocs=N nsenders=K check=OK|FAIL" (receiver) + * "DEBUG rank=X nprocs=N target=R check=OK" (senders) + * No MPI in this binary. + * + * A rank is the receiver iff its parsed fields contain "nsenders". + * + * Properties checked: + * Coverage — every rank 0..N-1 appears exactly once. + * Roles — exactly one receiver (master_rank), N-1 senders. + * Targets — every sender targets master_rank. + * Count — receiver's nsenders equals N-1. + * NonDefault — -mrank flag correctly changes the receiver. + * DataIntegrity — every rank reports check=OK. + * Agreement — incast_nb / incast_bsnbr / incast_get / incast_put report + * the same topology as incast_b. + * + * Requires 8 MPI ranks. + */ +#include +#include +#include "popen_helpers.h" + +using blink::DebugMap; +using blink::DebugFields; +using blink::run_debug; +using blink::get_int; +using blink::get_str; +using blink::check_all_ok; +using blink::check_coverage; + +/* ── role helpers ───────────────────────────────────────────────────────────── */ + +static bool is_receiver(const DebugMap& m, int r) +{ + auto it = m.find(r); + if (it == m.end()) return false; + return it->second.count("nsenders") > 0; +} + +static void check_roles(const DebugMap& m, int n, int master, const std::string& ctx) +{ + int receiver_count = 0; + for (const auto& [r, fields] : m) { + if (fields.count("nsenders")) { + receiver_count++; + EXPECT_EQ(r, master) + << ctx << ": rank " << r << " is receiver but master_rank=" << master; + int nsenders = get_int(m, r, "nsenders"); + EXPECT_EQ(nsenders, n - 1) + << ctx << ": receiver reports nsenders=" << nsenders + << " expected " << (n - 1); + } else { + int target = get_int(m, r, "target"); + EXPECT_EQ(target, master) + << ctx << ": rank " << r << " sender targets " << target + << " expected master=" << master; + } + } + EXPECT_EQ(receiver_count, 1) << ctx << ": expected exactly 1 receiver"; +} + +/* ── incast_b ───────────────────────────────────────────────────────────────── */ + +class IncastBTest : public ::testing::Test { +protected: + static constexpr int N = 8; + static constexpr int MASTER = 0; +}; + +TEST_F(IncastBTest, Coverage) { + auto m = run_debug("incast_b", N); + check_coverage(m, N, "incast_b"); + check_all_ok(m, "incast_b"); +} +TEST_F(IncastBTest, Roles) { + auto m = run_debug("incast_b", N); + check_roles(m, N, MASTER, "incast_b"); +} +TEST_F(IncastBTest, Burst) { + auto m = run_debug("incast_b", N, "-blength 0.001"); + check_coverage(m, N, "incast_b burst"); + check_roles(m, N, MASTER, "incast_b burst"); + check_all_ok(m, "incast_b burst"); +} +TEST_F(IncastBTest, NonDefaultMaster) { + const int ALT = 3; + auto m = run_debug("incast_b", N, "-mrank " + std::to_string(ALT)); + check_coverage(m, N, "incast_b mrank=3"); + check_roles(m, N, ALT, "incast_b mrank=3"); + check_all_ok(m, "incast_b mrank=3"); +} + +/* ── incast_nb ──────────────────────────────────────────────────────────────── */ + +class IncastNbTest : public ::testing::Test { +protected: + static constexpr int N = 8; + static constexpr int MASTER = 0; +}; + +TEST_F(IncastNbTest, Coverage) { + auto m = run_debug("incast_nb", N); + check_coverage(m, N, "incast_nb"); + check_all_ok(m, "incast_nb"); +} +TEST_F(IncastNbTest, Roles) { + auto m = run_debug("incast_nb", N); + check_roles(m, N, MASTER, "incast_nb"); +} +TEST_F(IncastNbTest, Burst) { + auto m = run_debug("incast_nb", N, "-blength 0.001"); + check_coverage(m, N, "incast_nb burst"); + check_roles(m, N, MASTER, "incast_nb burst"); + check_all_ok(m, "incast_nb burst"); +} +TEST_F(IncastNbTest, NonDefaultMaster) { + const int ALT = 3; + auto m = run_debug("incast_nb", N, "-mrank " + std::to_string(ALT)); + check_coverage(m, N, "incast_nb mrank=3"); + check_roles(m, N, ALT, "incast_nb mrank=3"); + check_all_ok(m, "incast_nb mrank=3"); +} +TEST_F(IncastNbTest, AgreesWith_IncastB) { + auto b = run_debug("incast_b", N); + auto nb = run_debug("incast_nb", N); + for (const auto& [r, fields_b] : b) { + if (!nb.count(r)) continue; + bool b_rx = is_receiver(b, r); + bool nb_rx = is_receiver(nb, r); + EXPECT_EQ(nb_rx, b_rx) + << "rank " << r << " role differs between incast_b and incast_nb"; + if (!b_rx) + EXPECT_EQ(get_int(nb, r, "target"), get_int(b, r, "target")) + << "rank " << r << " target differs between incast_b and incast_nb"; + } +} + +/* ── incast_bsnbr ───────────────────────────────────────────────────────────── */ + +class IncastBsnbrTest : public ::testing::Test { +protected: + static constexpr int N = 8; + static constexpr int MASTER = 0; +}; + +TEST_F(IncastBsnbrTest, Coverage) { + auto m = run_debug("incast_bsnbr", N); + check_coverage(m, N, "incast_bsnbr"); + check_all_ok(m, "incast_bsnbr"); +} +TEST_F(IncastBsnbrTest, Roles) { + auto m = run_debug("incast_bsnbr", N); + check_roles(m, N, MASTER, "incast_bsnbr"); +} +TEST_F(IncastBsnbrTest, Burst) { + auto m = run_debug("incast_bsnbr", N, "-blength 0.001"); + check_coverage(m, N, "incast_bsnbr burst"); + check_roles(m, N, MASTER, "incast_bsnbr burst"); + check_all_ok(m, "incast_bsnbr burst"); +} +TEST_F(IncastBsnbrTest, NonDefaultMaster) { + const int ALT = 3; + auto m = run_debug("incast_bsnbr", N, "-mrank " + std::to_string(ALT)); + check_coverage(m, N, "incast_bsnbr mrank=3"); + check_roles(m, N, ALT, "incast_bsnbr mrank=3"); + check_all_ok(m, "incast_bsnbr mrank=3"); +} +TEST_F(IncastBsnbrTest, AgreesWith_IncastB) { + auto b = run_debug("incast_b", N); + auto bsnbr = run_debug("incast_bsnbr", N); + for (const auto& [r, fields_b] : b) { + if (!bsnbr.count(r)) continue; + bool b_rx = is_receiver(b, r); + bool bs_rx = is_receiver(bsnbr, r); + EXPECT_EQ(bs_rx, b_rx) + << "rank " << r << " role differs between incast_b and incast_bsnbr"; + if (!b_rx) + EXPECT_EQ(get_int(bsnbr, r, "target"), get_int(b, r, "target")) + << "rank " << r << " target differs between incast_b and incast_bsnbr"; + } +} + +/* ── incast_get ─────────────────────────────────────────────────────────────── */ + +class IncastGetTest : public ::testing::Test { +protected: + static constexpr int N = 8; + static constexpr int MASTER = 0; +}; + +TEST_F(IncastGetTest, Coverage) { + auto m = run_debug("incast_get", N); + check_coverage(m, N, "incast_get"); + check_all_ok(m, "incast_get"); +} +TEST_F(IncastGetTest, Roles) { + auto m = run_debug("incast_get", N); + check_roles(m, N, MASTER, "incast_get"); +} +TEST_F(IncastGetTest, Burst) { + auto m = run_debug("incast_get", N, "-blength 0.001"); + check_coverage(m, N, "incast_get burst"); + check_roles(m, N, MASTER, "incast_get burst"); + check_all_ok(m, "incast_get burst"); +} +TEST_F(IncastGetTest, NonDefaultMaster) { + const int ALT = 3; + auto m = run_debug("incast_get", N, "-mrank " + std::to_string(ALT)); + check_coverage(m, N, "incast_get mrank=3"); + check_roles(m, N, ALT, "incast_get mrank=3"); + check_all_ok(m, "incast_get mrank=3"); +} +TEST_F(IncastGetTest, AgreesWith_IncastB) { + auto b = run_debug("incast_b", N); + auto get = run_debug("incast_get", N); + for (const auto& [r, fields_b] : b) { + if (!get.count(r)) continue; + bool b_rx = is_receiver(b, r); + bool g_rx = is_receiver(get, r); + EXPECT_EQ(g_rx, b_rx) + << "rank " << r << " role differs between incast_b and incast_get"; + if (!b_rx) + EXPECT_EQ(get_int(get, r, "target"), get_int(b, r, "target")) + << "rank " << r << " target differs between incast_b and incast_get"; + } +} + +/* ── incast_put ─────────────────────────────────────────────────────────────── */ + +class IncastPutTest : public ::testing::Test { +protected: + static constexpr int N = 8; + static constexpr int MASTER = 0; +}; + +TEST_F(IncastPutTest, Coverage) { + auto m = run_debug("incast_put", N); + check_coverage(m, N, "incast_put"); + check_all_ok(m, "incast_put"); +} +TEST_F(IncastPutTest, Roles) { + auto m = run_debug("incast_put", N); + check_roles(m, N, MASTER, "incast_put"); +} +TEST_F(IncastPutTest, Burst) { + auto m = run_debug("incast_put", N, "-blength 0.001"); + check_coverage(m, N, "incast_put burst"); + check_roles(m, N, MASTER, "incast_put burst"); + check_all_ok(m, "incast_put burst"); +} +TEST_F(IncastPutTest, NonDefaultMaster) { + const int ALT = 3; + auto m = run_debug("incast_put", N, "-mrank " + std::to_string(ALT)); + check_coverage(m, N, "incast_put mrank=3"); + check_roles(m, N, ALT, "incast_put mrank=3"); + check_all_ok(m, "incast_put mrank=3"); +} +TEST_F(IncastPutTest, AgreesWith_IncastB) { + auto b = run_debug("incast_b", N); + auto put = run_debug("incast_put", N); + for (const auto& [r, fields_b] : b) { + if (!put.count(r)) continue; + bool b_rx = is_receiver(b, r); + bool p_rx = is_receiver(put, r); + EXPECT_EQ(p_rx, b_rx) + << "rank " << r << " role differs between incast_b and incast_put"; + if (!b_rx) + EXPECT_EQ(get_int(put, r, "target"), get_int(b, r, "target")) + << "rank " << r << " target differs between incast_b and incast_put"; + } +} diff --git a/tests/test_kpartners.cpp b/tests/test_kpartners.cpp new file mode 100644 index 000000000..4c10fc73c --- /dev/null +++ b/tests/test_kpartners.cpp @@ -0,0 +1,72 @@ +/* + * test_kpartners.cpp — correctness tests for kpartners_nb. + * + * Strategy: spawn kpartners_nb with -debug and parse + * "DEBUG rank=X nprocs=Y k=K check=OK|FAIL" lines. + * + * Properties checked: + * Coverage — every rank 0..N-1 appears exactly once. + * Nprocs — every rank reports the correct communicator size. + * K — every rank reports the expected number of partners. + * DataIntegrity — every rank reports check=OK. In the new schema this is + * a meaningful check that partner identity was honoured. + * + * Requires 8 MPI ranks. + */ +#include +#include +#include "popen_helpers.h" + +using blink::DebugMap; +using blink::run_debug; +using blink::get_int; +using blink::check_all_ok; +using blink::check_coverage; +using blink::check_nprocs; + +/* ── helpers ────────────────────────────────────────────────────────────────── */ + +static void check_k(const DebugMap& m, int expected_k, const std::string& ctx) +{ + for (const auto& [r, fields] : m) { + int k = get_int(m, r, "k"); + EXPECT_EQ(k, expected_k) + << ctx << ": rank " << r << " reports k=" << k; + } +} + +/* ── tests ──────────────────────────────────────────────────────────────────── */ + +class KpartnersTest : public ::testing::Test { +protected: + static constexpr int N = 8; +}; + +TEST_F(KpartnersTest, Coverage) { + auto m = run_debug("kpartners_nb", N); + check_coverage(m, N, "k=1"); +} +TEST_F(KpartnersTest, Nprocs) { + auto m = run_debug("kpartners_nb", N); + check_nprocs(m, N, "k=1"); +} +TEST_F(KpartnersTest, K_default) { + auto m = run_debug("kpartners_nb", N); + check_k(m, 1, "k=1"); +} +TEST_F(KpartnersTest, DataIntegrity_k1) { + auto m = run_debug("kpartners_nb", N); + check_coverage(m, N, "k=1"); + check_all_ok(m, "k=1"); +} +TEST_F(KpartnersTest, DataIntegrity_k3) { + auto m = run_debug("kpartners_nb", N, "-k 3"); + check_coverage(m, N, "k=3"); + check_k(m, 3, "k=3"); + check_all_ok(m, "k=3"); +} +TEST_F(KpartnersTest, DataIntegrity_burst) { + auto m = run_debug("kpartners_nb", N, "-blength 0.001"); + check_coverage(m, N, "k=1 burst"); + check_all_ok(m, "k=1 burst"); +} diff --git a/tests/test_misc.cpp b/tests/test_misc.cpp new file mode 100644 index 000000000..043851c2c --- /dev/null +++ b/tests/test_misc.cpp @@ -0,0 +1,208 @@ +/* + * test_misc.cpp — miscellaneous correctness tests: + * - ring-buffer wrap (iter > maxsamples) + * - pretty-print formatter doesn't crash and produces a recognisable table + * - -mrand picks a deterministic master rank for a given seed + * - sampler/dist_test runs (validates the burst distribution implementation) + * + * All tests use 8 ranks except where noted. + */ +#include +#include +#include +#include +#include +#include "popen_helpers.h" + +using blink::run_debug_capture; +using blink::DebugRun; + +/* Helper: count lines in s containing a substring */ +static int count_lines_containing(const std::string& s, const std::string& needle) +{ + int n = 0; + size_t p = 0; + while ((p = s.find(needle, p)) != std::string::npos) { n++; p += needle.size(); } + return n; +} + +/* ── ring-buffer wrap ───────────────────────────────────────────────────────── */ + +/* + * Configure max_samples to be smaller than the iteration count, ensuring the + * ring buffer wraps. The benchmark should still print exactly max_samples + * "Average,..." data lines (one per recorded sample), and the summary line + * should report "Measured " iterations. + */ +TEST(RingBufferWrap, BarrierWrapsCorrectly) +{ + /* run barrier_nb (cheap) with iter=50 maxsamples=10 warmup=0 — buffer + * should hold the last 10 samples. */ + blink::DebugRun r = run_debug_capture("barrier_nb", 8, + "-iter 50 -maxsamples 10 -warmup 0"); + /* run_debug_capture does NOT assert; do the assertions explicitly */ + ASSERT_TRUE(r.normal_exit) << "stderr:\n" << r.stderr_raw; + ASSERT_EQ(r.exit_code, 0) << "stderr:\n" << r.stderr_raw; + + /* the master rank's CSV body has lines of the form "%.9f,%.9f,..." */ + int data_lines = 0; + size_t p = 0; + while ((p = r.stdout_raw.find('\n', p)) != std::string::npos) { + /* heuristic: a data line starts with a digit and has 4 commas */ + size_t line_start = r.stdout_raw.rfind('\n', p > 0 ? p - 1 : 0); + size_t s = (line_start == std::string::npos) ? 0 : line_start + 1; + std::string line = r.stdout_raw.substr(s, p - s); + if (!line.empty() && isdigit((unsigned char)line[0])) + if (std::count(line.begin(), line.end(), ',') == 4) + data_lines++; + p++; + } + EXPECT_EQ(data_lines, 10) + << "ring buffer wrap: expected 10 measured samples (maxsamples), got " + << data_lines << "\nstdout:\n" << r.stdout_raw; + + EXPECT_NE(r.stdout_raw.find("Ran 50 iterations. Measured 10 iterations."), + std::string::npos) + << "summary line mismatch.\nstdout:\n" << r.stdout_raw; +} + +/* ── pretty-print formatter ─────────────────────────────────────────────────── */ + +TEST(PrettyPrint, FormatterDoesNotCrash) +{ + DebugRun r = run_debug_capture("barrier_nb", 8, "-iter 3 -pretty-print"); + ASSERT_TRUE(r.normal_exit) << "stderr:\n" << r.stderr_raw; + ASSERT_EQ(r.exit_code, 0); + /* pretty-print uses a column-header row and a separator line */ + EXPECT_NE(r.stdout_raw.find("avg"), std::string::npos); + EXPECT_NE(r.stdout_raw.find("median"), std::string::npos); + EXPECT_NE(r.stdout_raw.find("samples"),std::string::npos); +} + +/* ── -mrand determinism ─────────────────────────────────────────────────────── */ + +TEST(MasterRand, SameSeedSameMaster) +{ + /* with -mrand and a fixed seed, the chosen master_rank should be + * deterministic across runs. We grep the "receiver rank: R" line from + * incast_b's own startup banner. */ + DebugRun r1 = run_debug_capture("incast_b", 8, "-mrand -seed 42"); + DebugRun r2 = run_debug_capture("incast_b", 8, "-mrand -seed 42"); + ASSERT_TRUE(r1.normal_exit); + ASSERT_TRUE(r2.normal_exit); + auto p1 = r1.stdout_raw.find("receiver rank:"); + auto p2 = r2.stdout_raw.find("receiver rank:"); + ASSERT_NE(p1, std::string::npos) << r1.stdout_raw; + ASSERT_NE(p2, std::string::npos) << r2.stdout_raw; + int m1, m2; + sscanf(r1.stdout_raw.c_str() + p1, "receiver rank: %d", &m1); + sscanf(r2.stdout_raw.c_str() + p2, "receiver rank: %d", &m2); + EXPECT_EQ(m1, m2) + << "same -seed should give same -mrand master, got " << m1 << " vs " << m2; +} + +/* ── dist_test sampler validation ───────────────────────────────────────────── */ + +TEST(SamplerValidation, DistTestRuns) +{ + /* dist_test is single-rank. If it exits 0 the sampler implementations + * are internally consistent (mean / KS test). */ + DebugRun r = run_debug_capture("dist_test", 1, ""); + /* dist_test may not accept -debug; treat absence of failure as success */ + ASSERT_EQ(r.exit_code, 0) + << "dist_test failed.\nstderr:\n" << r.stderr_raw + << "\nstdout:\n" << r.stdout_raw; +} + +/* ── warm-up exclusion ──────────────────────────────────────────────────────── */ + +/* + * Warm-up iterations must NOT be recorded. With -iter 5 -warmup 3 the benchmark + * runs 8 outer iterations but records only the last 5; the summary must report + * "Ran 8 iterations. Measured 5 iterations." and emit exactly 5 CSV data lines. + */ +TEST(WarmupExclusion, WarmupSamplesNotRecorded) +{ + DebugRun r = run_debug_capture("barrier_nb", 8, "-iter 5 -warmup 3 -maxsamples 1000"); + ASSERT_TRUE(r.normal_exit) << "stderr:\n" << r.stderr_raw; + ASSERT_EQ(r.exit_code, 0) << "stderr:\n" << r.stderr_raw; + + int data_lines = 0; + size_t p = 0; + while ((p = r.stdout_raw.find('\n', p)) != std::string::npos) { + size_t line_start = r.stdout_raw.rfind('\n', p > 0 ? p - 1 : 0); + size_t s = (line_start == std::string::npos) ? 0 : line_start + 1; + std::string line = r.stdout_raw.substr(s, p - s); + if (!line.empty() && isdigit((unsigned char)line[0])) + if (std::count(line.begin(), line.end(), ',') == 4) + data_lines++; + p++; + } + EXPECT_EQ(data_lines, 5) + << "expected 5 recorded samples (3 warmup excluded), got " << data_lines + << "\nstdout:\n" << r.stdout_raw; + EXPECT_NE(r.stdout_raw.find("Ran 8 iterations. Measured 5 iterations."), + std::string::npos) + << "summary should report 8 run / 5 measured.\nstdout:\n" << r.stdout_raw; +} + +/* ── SIGUSR1 clean shutdown ─────────────────────────────────────────────────── */ + +/* + * Regression test for the endless-mode SIGUSR1 shutdown. The shutdown flag is + * set only on the rank that receives the signal, so all ranks must agree + * collectively (check_shutdown) to leave the measurement loop on the same + * iteration. Sending SIGUSR1 to exactly ONE rank of an endless run must shut + * the whole job down cleanly (no deadlock) and still print collected results. + * + * Orchestrated in POSIX sh via popen: launch the endless run in the background, + * signal one rank (a process whose comm is exactly "alltoall_nb" — i.e. a rank, + * not the launcher), then wait up to 30s for a clean exit, killing it if it + * hangs so the test itself can never block indefinitely. + */ +TEST(Shutdown, Sigusr1ToSingleRankShutsDownCleanly) +{ + char outtmpl[] = "/tmp/blink_endl_XXXXXX"; + int ofd = mkstemp(outtmpl); + ASSERT_GE(ofd, 0); + close(ofd); + std::string outfile = outtmpl; + std::string bin = blink::bin_dir() + "/alltoall_nb"; + + std::string cmd = + "\"" + blink::mpiexec() + "\" " + blink::nproc_flag() + " 4 \"" + bin + "\"" + " -endl -msgsize 1024 -seed 999983 > \"" + outfile + "\" 2>&1 &\n" + "MPIPID=$!\n" + "sleep 3\n" + "RP=\"\"\n" + "for q in $(pgrep -f 'alltoall_nb -endl'); do\n" + " if [ -r /proc/$q/comm ] && [ \"$(cat /proc/$q/comm)\" = alltoall_nb ]; then RP=$q; break; fi\n" + "done\n" + "[ -n \"$RP\" ] && kill -USR1 \"$RP\"\n" + "CLEAN=HANG\n" + "for i in $(seq 1 30); do kill -0 \"$MPIPID\" 2>/dev/null || { CLEAN=CLEAN; break; }; sleep 1; done\n" + "if [ \"$CLEAN\" = HANG ]; then kill -9 \"$MPIPID\" 2>/dev/null; pkill -9 -f 'alltoall_nb -endl' 2>/dev/null; fi\n" + "echo SIGNALED=$RP\n" + "echo RESULT=$CLEAN\n"; + + FILE *pp = popen(cmd.c_str(), "r"); + ASSERT_NE(pp, nullptr); + std::string out; + char buf[512]; + size_t n; + while ((n = fread(buf, 1, sizeof(buf), pp)) > 0) out.append(buf, n); + pclose(pp); + + std::string bench; + FILE *of = fopen(outfile.c_str(), "r"); + if (of) { while ((n = fread(buf, 1, sizeof(buf), of)) > 0) bench.append(buf, n); fclose(of); } + unlink(outfile.c_str()); + + EXPECT_EQ(out.find("SIGNALED=\n"), std::string::npos) + << "no rank process was found to signal.\norchestration:\n" << out; + EXPECT_NE(out.find("RESULT=CLEAN"), std::string::npos) + << "endless job did not shut down after SIGUSR1 to one rank (deadlock?).\n" + << "orchestration:\n" << out << "\nbenchmark output:\n" << bench; + EXPECT_NE(bench.find("Measured"), std::string::npos) + << "no results footer after clean shutdown.\nbenchmark output:\n" << bench; +} diff --git a/tests/test_pairwise.cpp b/tests/test_pairwise.cpp new file mode 100644 index 000000000..6d86291b7 --- /dev/null +++ b/tests/test_pairwise.cpp @@ -0,0 +1,251 @@ +/* + * test_pairwise.cpp — correctness tests for pairwise benchmarks. + * + * Strategy: spawn the *actual* benchmark binary under mpirun with -debug, + * parse the "DEBUG rank=X nprocs=N partner=Y check=OK|FAIL" lines it emits, + * and assert the pairing properties below. Breaking pairwise_b.c / + * pairwise_nb.c / pairwise_bsnbr.c will break these tests. + * + * Properties checked: + * Coverage — every rank 0..N-1 appears exactly once in the output. + * Validity — every reported partner is in [0, N-1]. + * Symmetry — A→B implies B→A (holds for offpair and rpair modes). + * Bijection — no partner value appears more than once. + * ExactPairs — for deterministic modes we verify the specific expected mapping. + * DataIntegrity — every rank reports check=OK. + */ +#include +#include +#include +#include +#include "popen_helpers.h" + +using blink::DebugMap; +using blink::run_debug; +using blink::get_int; +using blink::check_all_ok; +using blink::check_coverage; + +/* ── shared assertion helpers ───────────────────────────────────────────────── */ + +static void check_validity(const DebugMap& m, int n, const std::string& ctx) +{ + for (const auto& [r, fields] : m) { + int p = get_int(m, r, "partner"); + EXPECT_GE(p, 0) << ctx << ": rank " << r << " has partner " << p << " < 0"; + EXPECT_LT(p, n) << ctx << ": rank " << r << " has partner " << p << " >= " << n; + } +} + +static void check_symmetric(const DebugMap& m, const std::string& ctx) +{ + for (const auto& [r, fields] : m) { + int p = get_int(m, r, "partner"); + auto it = m.find(p); + if (it == m.end()) continue; /* coverage failures reported elsewhere */ + int pp = get_int(m, p, "partner"); + EXPECT_EQ(pp, r) + << ctx << ": rank " << r << "→" << p + << " but rank " << p << "→" << pp << " (not symmetric)"; + } +} + +static void check_bijection(const DebugMap& m, const std::string& ctx) +{ + std::set seen; + for (const auto& [r, fields] : m) { + int p = get_int(m, r, "partner"); + EXPECT_TRUE(seen.insert(p).second) + << ctx << ": partner " << p << " assigned to more than one rank"; + } +} + +static void check_exact(const DebugMap& m, + const std::map& expected, + const std::string& ctx) +{ + for (const auto& [r, p] : expected) { + auto it = m.find(r); + if (it == m.end()) continue; /* coverage failures reported elsewhere */ + int got = get_int(m, r, "partner"); + EXPECT_EQ(got, p) + << ctx << ": rank " << r << " expected partner " << p + << " but got " << got; + } +} + +/* ── pairwise_b ─────────────────────────────────────────────────────────────── */ + +class PairwiseBTest : public ::testing::Test { +protected: + static constexpr int N = 8; +}; + +TEST_F(PairwiseBTest, Offpair_Offset1_Coverage) { + auto m = run_debug("pairwise_b", N, "-mode offpair -offset 1"); + check_coverage(m, N, "offpair offset=1"); + check_all_ok(m, "offpair offset=1"); +} +TEST_F(PairwiseBTest, Offpair_Offset1_Validity) { + auto m = run_debug("pairwise_b", N, "-mode offpair -offset 1"); + check_validity(m, N, "offpair offset=1"); +} +TEST_F(PairwiseBTest, Offpair_Offset1_Symmetric) { + auto m = run_debug("pairwise_b", N, "-mode offpair -offset 1"); + check_symmetric(m, "offpair offset=1"); +} +TEST_F(PairwiseBTest, Offpair_Offset1_ExactPairs) { + auto m = run_debug("pairwise_b", N, "-mode offpair -offset 1"); + /* offset_pairs(n=8, offset=1) → 0↔1, 2↔3, 4↔5, 6↔7 */ + check_exact(m, {{0,1},{1,0},{2,3},{3,2},{4,5},{5,4},{6,7},{7,6}}, "offpair offset=1"); +} + +TEST_F(PairwiseBTest, Offpair_Offset2_Symmetric) { + auto m = run_debug("pairwise_b", N, "-mode offpair -offset 2"); + check_symmetric(m, "offpair offset=2"); +} +TEST_F(PairwiseBTest, Offpair_Offset2_ExactPairs) { + auto m = run_debug("pairwise_b", N, "-mode offpair -offset 2"); + /* offset_pairs(n=8, offset=2) → 0↔2, 1↔3, 4↔6, 5↔7 */ + check_exact(m, {{0,2},{2,0},{1,3},{3,1},{4,6},{6,4},{5,7},{7,5}}, "offpair offset=2"); +} + +TEST_F(PairwiseBTest, RandomPair_Coverage) { + auto m = run_debug("pairwise_b", N, "-mode rpair -seed 1"); + check_coverage(m, N, "rpair"); + check_all_ok(m, "rpair"); +} +TEST_F(PairwiseBTest, RandomPair_Validity) { + auto m = run_debug("pairwise_b", N, "-mode rpair -seed 1"); + check_validity(m, N, "rpair"); +} +TEST_F(PairwiseBTest, RandomPair_Symmetric) { + auto m = run_debug("pairwise_b", N, "-mode rpair -seed 1"); + check_symmetric(m, "rpair"); +} +TEST_F(PairwiseBTest, RandomPair_Bijection) { + auto m = run_debug("pairwise_b", N, "-mode rpair -seed 1"); + check_bijection(m, "rpair"); +} +TEST_F(PairwiseBTest, Burst) { + auto m = run_debug("pairwise_b", N, "-mode offpair -offset 1 -blength 0.001"); + check_coverage(m, N, "pairwise_b burst"); + check_symmetric(m, "pairwise_b burst"); + check_all_ok(m, "pairwise_b burst"); +} + +/* ── non-reciprocal modes (rot / perm) ────────────────────────────────────── + * rot and perm are permutations, not symmetric pairings, so the partner graph + * is a bijection but NOT symmetric (A→B does not imply B→A). These exercise + * the debug path that must avoid deadlock with a non-reciprocal partner. */ +TEST_F(PairwiseBTest, Rot_Offset1_Coverage) { + auto m = run_debug("pairwise_b", N, "-mode rot -offset 1"); + check_coverage(m, N, "rot offset=1"); + check_all_ok(m, "rot offset=1"); +} +TEST_F(PairwiseBTest, Rot_Offset1_Validity) { + auto m = run_debug("pairwise_b", N, "-mode rot -offset 1"); + check_validity(m, N, "rot offset=1"); +} +TEST_F(PairwiseBTest, Rot_Offset1_Bijection) { + auto m = run_debug("pairwise_b", N, "-mode rot -offset 1"); + check_bijection(m, "rot offset=1"); +} +TEST_F(PairwiseBTest, Rot_Offset1_ExactPairs) { + auto m = run_debug("pairwise_b", N, "-mode rot -offset 1"); + /* rot: rank r sends to (r+1)%N */ + check_exact(m, {{0,1},{1,2},{2,3},{3,4},{4,5},{5,6},{6,7},{7,0}}, "rot offset=1"); +} +TEST_F(PairwiseBTest, Perm_Coverage) { + auto m = run_debug("pairwise_b", N, "-mode perm -seed 1"); + check_coverage(m, N, "perm"); + check_all_ok(m, "perm"); +} +TEST_F(PairwiseBTest, Perm_Validity) { + auto m = run_debug("pairwise_b", N, "-mode perm -seed 1"); + check_validity(m, N, "perm"); +} +TEST_F(PairwiseBTest, Perm_Bijection) { + auto m = run_debug("pairwise_b", N, "-mode perm -seed 1"); + check_bijection(m, "perm"); +} + +/* ── pairwise_nb — same pairing logic, non-blocking MPI ────────────────────── */ + +class PairwiseNbTest : public ::testing::Test { +protected: + static constexpr int N = 8; +}; + +TEST_F(PairwiseNbTest, Offpair_Offset1_Symmetric) { + auto m = run_debug("pairwise_nb", N, "-mode offpair -offset 1"); + check_symmetric(m, "nb offpair offset=1"); + check_all_ok(m, "nb offpair offset=1"); +} +TEST_F(PairwiseNbTest, Offpair_Offset1_ExactPairs) { + auto m = run_debug("pairwise_nb", N, "-mode offpair -offset 1"); + check_exact(m, {{0,1},{1,0},{2,3},{3,2},{4,5},{5,4},{6,7},{7,6}}, "nb offpair offset=1"); +} +TEST_F(PairwiseNbTest, RandomPair_Symmetric) { + auto m = run_debug("pairwise_nb", N, "-mode rpair -seed 1"); + check_symmetric(m, "nb rpair"); + check_all_ok(m, "nb rpair"); +} +TEST_F(PairwiseNbTest, Burst) { + auto m = run_debug("pairwise_nb", N, "-mode offpair -offset 1 -blength 0.001"); + check_coverage(m, N, "pairwise_nb burst"); + check_symmetric(m, "pairwise_nb burst"); + check_all_ok(m, "pairwise_nb burst"); +} +TEST_F(PairwiseNbTest, Rot_Offset1_DataIntegrity) { + auto m = run_debug("pairwise_nb", N, "-mode rot -offset 1"); + check_coverage(m, N, "nb rot offset=1"); + check_bijection(m, "nb rot offset=1"); + check_all_ok(m, "nb rot offset=1"); +} +TEST_F(PairwiseNbTest, Perm_DataIntegrity) { + auto m = run_debug("pairwise_nb", N, "-mode perm -seed 1"); + check_coverage(m, N, "nb perm"); + check_bijection(m, "nb perm"); + check_all_ok(m, "nb perm"); +} + +/* ── pairwise_bsnbr — same pairing logic, non-blocking send / blocking recv ── */ + +class PairwiseBsnbrTest : public ::testing::Test { +protected: + static constexpr int N = 8; +}; + +TEST_F(PairwiseBsnbrTest, Offpair_Offset1_Symmetric) { + auto m = run_debug("pairwise_bsnbr", N, "-mode offpair -offset 1"); + check_symmetric(m, "bsnbr offpair offset=1"); + check_all_ok(m, "bsnbr offpair offset=1"); +} +TEST_F(PairwiseBsnbrTest, Offpair_Offset1_ExactPairs) { + auto m = run_debug("pairwise_bsnbr", N, "-mode offpair -offset 1"); + check_exact(m, {{0,1},{1,0},{2,3},{3,2},{4,5},{5,4},{6,7},{7,6}}, "bsnbr offpair offset=1"); +} +TEST_F(PairwiseBsnbrTest, RandomPair_Symmetric) { + auto m = run_debug("pairwise_bsnbr", N, "-mode rpair -seed 1"); + check_symmetric(m, "bsnbr rpair"); + check_all_ok(m, "bsnbr rpair"); +} +TEST_F(PairwiseBsnbrTest, Burst) { + auto m = run_debug("pairwise_bsnbr", N, "-mode offpair -offset 1 -blength 0.001"); + check_coverage(m, N, "pairwise_bsnbr burst"); + check_symmetric(m, "pairwise_bsnbr burst"); + check_all_ok(m, "pairwise_bsnbr burst"); +} +TEST_F(PairwiseBsnbrTest, Rot_Offset1_DataIntegrity) { + auto m = run_debug("pairwise_bsnbr", N, "-mode rot -offset 1"); + check_coverage(m, N, "bsnbr rot offset=1"); + check_bijection(m, "bsnbr rot offset=1"); + check_all_ok(m, "bsnbr rot offset=1"); +} +TEST_F(PairwiseBsnbrTest, Perm_DataIntegrity) { + auto m = run_debug("pairwise_bsnbr", N, "-mode perm -seed 1"); + check_coverage(m, N, "bsnbr perm"); + check_bijection(m, "bsnbr perm"); + check_all_ok(m, "bsnbr perm"); +} diff --git a/tests/test_pingpong.cpp b/tests/test_pingpong.cpp new file mode 100644 index 000000000..5857a6f90 --- /dev/null +++ b/tests/test_pingpong.cpp @@ -0,0 +1,78 @@ +/* + * test_pingpong.cpp — correctness tests for pingpong benchmarks. + * + * Strategy: spawn the benchmark with -debug and parse + * "DEBUG rank=X nprocs=Y [partner=Z] check=OK|FAIL" lines. + * + * Properties checked: + * Coverage — every rank 0..N-1 appears exactly once. + * Nprocs — every rank reports the correct communicator size. + * DataIntegrity — every rank reports check=OK. + * Each rank fills send_buf with its own rank byte; + * after the ping-pong it verifies recv_buf contains + * the partner's rank byte throughout. + * + * pingpong_b requires exactly 2 MPI ranks. + * pingpong_pairwise_b requires 8 MPI ranks (even number). + */ +#include +#include +#include "popen_helpers.h" + +using blink::run_debug; +using blink::check_all_ok; +using blink::check_coverage; +using blink::check_nprocs; + +/* ── pingpong_b (2 ranks) ───────────────────────────────────────────────────── */ + +class PingpongBTest : public ::testing::Test { +protected: + static constexpr int N = 2; +}; + +TEST_F(PingpongBTest, Coverage) { + auto m = run_debug("pingpong_b", N); + check_coverage(m, N, "pingpong_b"); +} +TEST_F(PingpongBTest, Nprocs) { + auto m = run_debug("pingpong_b", N); + check_nprocs(m, N, "pingpong_b"); +} +TEST_F(PingpongBTest, DataIntegrity) { + auto m = run_debug("pingpong_b", N); + check_coverage(m, N, "pingpong_b"); + check_all_ok(m, "pingpong_b"); +} + +TEST_F(PingpongBTest, DataIntegrity_burst) { + auto m = run_debug("pingpong_b", N, "-blength 0.001"); + check_coverage(m, N, "pingpong_b burst"); + check_all_ok(m, "pingpong_b burst"); +} + +/* ── pingpong_pairwise_b (8 ranks) ─────────────────────────────────────────── */ + +class PingpongPairwiseBTest : public ::testing::Test { +protected: + static constexpr int N = 8; +}; + +TEST_F(PingpongPairwiseBTest, Coverage) { + auto m = run_debug("pingpong_pairwise_b", N); + check_coverage(m, N, "pingpong_pairwise_b"); +} +TEST_F(PingpongPairwiseBTest, Nprocs) { + auto m = run_debug("pingpong_pairwise_b", N); + check_nprocs(m, N, "pingpong_pairwise_b"); +} +TEST_F(PingpongPairwiseBTest, DataIntegrity) { + auto m = run_debug("pingpong_pairwise_b", N); + check_coverage(m, N, "pingpong_pairwise_b"); + check_all_ok(m, "pingpong_pairwise_b"); +} +TEST_F(PingpongPairwiseBTest, DataIntegrity_burst) { + auto m = run_debug("pingpong_pairwise_b", N, "-blength 0.001"); + check_coverage(m, N, "pingpong_pairwise_b burst"); + check_all_ok(m, "pingpong_pairwise_b burst"); +} diff --git a/tests/test_ring.cpp b/tests/test_ring.cpp new file mode 100644 index 000000000..1fad574ab --- /dev/null +++ b/tests/test_ring.cpp @@ -0,0 +1,232 @@ +/* + * test_ring.cpp — correctness tests for ring benchmarks. + * + * Strategy: spawn the actual benchmark binary with -debug and parse + * "DEBUG rank=X nprocs=N left=L right=R check=OK|FAIL" lines. + * No MPI in this binary. + * + * Properties checked: + * Coverage — every rank 0..N-1 appears exactly once. + * Validity — left and right are valid ranks in [0, N-1]. + * Exact values — sequential ring: rank r has left=(r-1+N)%N, right=(r+1)%N. + * Consistency — if rank A says right=B then rank B must say left=A, + * and vice-versa (the neighbour relation is coherent). + * Completeness — every rank appears as someone's left exactly once AND + * as someone's right exactly once (proper ring, no orphans). + * DataIntegrity — every rank reports check=OK. + * + * Requires 8 MPI ranks. + */ +#include +#include +#include +#include +#include "popen_helpers.h" + +using blink::DebugMap; +using blink::run_debug; +using blink::get_int; +using blink::check_all_ok; +using blink::check_coverage; + +/* ── shared assertion helpers ───────────────────────────────────────────────── */ + +static void check_validity(const DebugMap& m, int n, const std::string& ctx) +{ + for (const auto& [r, fields] : m) { + int l = get_int(m, r, "left"); + int rr = get_int(m, r, "right"); + EXPECT_GE(l, 0) << ctx << ": rank " << r << " left=" << l; + EXPECT_LT(l, n) << ctx << ": rank " << r << " left=" << l; + EXPECT_GE(rr, 0) << ctx << ": rank " << r << " right=" << rr; + EXPECT_LT(rr, n) << ctx << ": rank " << r << " right=" << rr; + } +} + +/* If A.right = B then B.left must equal A (and A.left = C implies C.right = A). */ +static void check_consistency(const DebugMap& m, const std::string& ctx) +{ + for (const auto& [r, fields] : m) { + int l = get_int(m, r, "left"); + int rr = get_int(m, r, "right"); + + /* check right edge: r → rr */ + auto it = m.find(rr); + if (it != m.end()) { + int neighbour_left = get_int(m, rr, "left"); + EXPECT_EQ(neighbour_left, r) + << ctx << ": rank " << r << " says right=" << rr + << " but rank " << rr << " says left=" << neighbour_left; + } + + /* check left edge: r → l */ + it = m.find(l); + if (it != m.end()) { + int neighbour_right = get_int(m, l, "right"); + EXPECT_EQ(neighbour_right, r) + << ctx << ": rank " << r << " says left=" << l + << " but rank " << l << " says right=" << neighbour_right; + } + } +} + +/* Every rank appears as someone's left exactly once AND right exactly once. */ +static void check_completeness(const DebugMap& m, const std::string& ctx) +{ + std::map as_left, as_right; + for (const auto& [r, fields] : m) { + as_left[get_int(m, r, "left")]++; + as_right[get_int(m, r, "right")]++; + } + for (const auto& [r, _] : m) { + EXPECT_EQ(as_left.count(r) ? as_left[r] : 0, 1) + << ctx << ": rank " << r << " appears as left neighbour " + << (as_left.count(r) ? as_left[r] : 0) << " time(s) (expected 1)"; + EXPECT_EQ(as_right.count(r) ? as_right[r] : 0, 1) + << ctx << ": rank " << r << " appears as right neighbour " + << (as_right.count(r) ? as_right[r] : 0) << " time(s) (expected 1)"; + } +} + +static void check_exact_sequential(const DebugMap& m, int n, const std::string& ctx) +{ + for (const auto& [r, fields] : m) { + int l = get_int(m, r, "left"); + int rr = get_int(m, r, "right"); + EXPECT_EQ(l, (r - 1 + n) % n) + << ctx << ": rank " << r << " expected left=" << (r-1+n)%n + << " got " << l; + EXPECT_EQ(rr, (r + 1) % n) + << ctx << ": rank " << r << " expected right=" << (r+1)%n + << " got " << rr; + } +} + +/* ── ring_nb ────────────────────────────────────────────────────────────────── */ + +class RingNbTest : public ::testing::Test { +protected: + static constexpr int N = 8; +}; + +TEST_F(RingNbTest, Sequential_Coverage) { + auto m = run_debug("ring_nb", N); + check_coverage(m, N, "ring_nb sequential"); + check_all_ok(m, "ring_nb sequential"); +} +TEST_F(RingNbTest, Sequential_Validity) { + auto m = run_debug("ring_nb", N); + check_validity(m, N, "ring_nb sequential"); +} +TEST_F(RingNbTest, Sequential_ExactNeighbors) { + auto m = run_debug("ring_nb", N); + check_exact_sequential(m, N, "ring_nb sequential"); +} +TEST_F(RingNbTest, Sequential_Consistency) { + auto m = run_debug("ring_nb", N); + check_consistency(m, "ring_nb sequential"); +} +TEST_F(RingNbTest, Sequential_Completeness) { + auto m = run_debug("ring_nb", N); + check_completeness(m, "ring_nb sequential"); +} + +TEST_F(RingNbTest, Random_Coverage) { + auto m = run_debug("ring_nb", N, "-rring -seed 1"); + check_coverage(m, N, "ring_nb random"); + check_all_ok(m, "ring_nb random"); +} +TEST_F(RingNbTest, Random_Validity) { + auto m = run_debug("ring_nb", N, "-rring -seed 1"); + check_validity(m, N, "ring_nb random"); +} +/* The random ring is built from an explicit position<->rank inverse map + * (positions[targets[i]] = i in ring_nb.c), so the neighbour relation is fully + * reciprocal (A.right=B <=> B.left=A) — the same consistency property the + * sequential ring has. Both consistency and completeness must hold. */ +TEST_F(RingNbTest, Random_Consistency) { + auto m = run_debug("ring_nb", N, "-rring -seed 1"); + check_consistency(m, "ring_nb random"); +} +TEST_F(RingNbTest, Random_Completeness) { + auto m = run_debug("ring_nb", N, "-rring -seed 1"); + check_completeness(m, "ring_nb random"); +} +TEST_F(RingNbTest, Burst) { + auto m = run_debug("ring_nb", N, "-blength 0.001"); + check_coverage(m, N, "ring_nb burst"); + check_consistency(m, "ring_nb burst"); + check_all_ok(m, "ring_nb burst"); +} +/* Random ring must differ from sequential (sanity: permutation actually happened). */ +TEST_F(RingNbTest, Random_DiffersFromSequential) { + auto seq = run_debug("ring_nb", N); + auto rnd = run_debug("ring_nb", N, "-rring -seed 1"); + bool any_diff = false; + for (const auto& [r, fields] : seq) { + if (!rnd.count(r)) continue; + if (get_int(rnd, r, "left") != get_int(seq, r, "left") || + get_int(rnd, r, "right") != get_int(seq, r, "right")) + any_diff = true; + } + EXPECT_TRUE(any_diff) << "random ring produced identical topology to sequential"; +} + +/* ── ring_bsnbr — same setup code, different MPI calls ─────────────────────── */ + +class RingBsnbrTest : public ::testing::Test { +protected: + static constexpr int N = 8; +}; + +TEST_F(RingBsnbrTest, Sequential_ExactNeighbors) { + auto m = run_debug("ring_bsnbr", N); + check_exact_sequential(m, N, "ring_bsnbr sequential"); + check_all_ok(m, "ring_bsnbr sequential"); +} +TEST_F(RingBsnbrTest, Sequential_Consistency) { + auto m = run_debug("ring_bsnbr", N); + check_consistency(m, "ring_bsnbr sequential"); +} +TEST_F(RingBsnbrTest, Sequential_Completeness) { + auto m = run_debug("ring_bsnbr", N); + check_completeness(m, "ring_bsnbr sequential"); +} +TEST_F(RingBsnbrTest, Random_Consistency) { + auto m = run_debug("ring_bsnbr", N, "-rring -seed 1"); + check_consistency(m, "ring_bsnbr random"); +} +TEST_F(RingBsnbrTest, Random_Completeness) { + auto m = run_debug("ring_bsnbr", N, "-rring -seed 1"); + check_completeness(m, "ring_bsnbr random"); + check_all_ok(m, "ring_bsnbr random"); +} +/* Both variants must agree on the same topology for the same seed. */ +TEST_F(RingBsnbrTest, AgreesWith_RingNb_Sequential) { + auto nb = run_debug("ring_nb", N); + auto bsnbr = run_debug("ring_bsnbr", N); + for (const auto& [r, fields] : nb) { + if (!bsnbr.count(r)) continue; + EXPECT_EQ(get_int(bsnbr, r, "left"), get_int(nb, r, "left")) + << "rank " << r << " left differs between ring_nb and ring_bsnbr"; + EXPECT_EQ(get_int(bsnbr, r, "right"), get_int(nb, r, "right")) + << "rank " << r << " right differs between ring_nb and ring_bsnbr"; + } +} +TEST_F(RingBsnbrTest, Burst) { + auto m = run_debug("ring_bsnbr", N, "-blength 0.001"); + check_coverage(m, N, "ring_bsnbr burst"); + check_consistency(m, "ring_bsnbr burst"); + check_all_ok(m, "ring_bsnbr burst"); +} +TEST_F(RingBsnbrTest, AgreesWith_RingNb_Random) { + auto nb = run_debug("ring_nb", N, "-rring -seed 42"); + auto bsnbr = run_debug("ring_bsnbr", N, "-rring -seed 42"); + for (const auto& [r, fields] : nb) { + if (!bsnbr.count(r)) continue; + EXPECT_EQ(get_int(bsnbr, r, "left"), get_int(nb, r, "left")) + << "rank " << r << " left differs (random, seed=42)"; + EXPECT_EQ(get_int(bsnbr, r, "right"), get_int(nb, r, "right")) + << "rank " << r << " right differs (random, seed=42)"; + } +} diff --git a/tests/test_stencil.cpp b/tests/test_stencil.cpp new file mode 100644 index 000000000..79db31e68 --- /dev/null +++ b/tests/test_stencil.cpp @@ -0,0 +1,140 @@ +/* + * test_stencil.cpp — correctness tests for stencil_2d_nb. + * + * Strategy: spawn stencil_2d_nb with -debug and parse + * "DEBUG rank=X nprocs=Y north=N south=S west=W east=E check=OK|FAIL" lines. + * Absent neighbours (boundary ranks, non-periodic) are printed as MPI_PROC_NULL + * which is negative; the test treats any negative value as "no neighbour". + * + * Properties checked: + * Coverage — every rank 0..N-1 appears exactly once. + * Nprocs — every rank reports the correct communicator size. + * DataIntegrity — every rank reports check=OK. + * For each present neighbour d, recv_buf[d*msg_size..] + * must equal neighbour's rank byte throughout. + * Consistency — the neighbour graph is symmetric: + * if rank A's north = B then rank B's south = A, etc. + * + * Requires 8 MPI ranks (auto grid via MPI_Dims_create). + */ +#include +#include +#include "popen_helpers.h" + +using blink::DebugMap; +using blink::run_debug; +using blink::get_int; +using blink::check_all_ok; +using blink::check_coverage; +using blink::check_nprocs; + +/* ── helpers ────────────────────────────────────────────────────────────────── */ + +static void check_consistency(const DebugMap& m, const std::string& ctx) +{ + for (const auto& [r, fields] : m) { + int north = get_int(m, r, "north"); + int south = get_int(m, r, "south"); + int west = get_int(m, r, "west"); + int east = get_int(m, r, "east"); + + if (north >= 0) { + ASSERT_TRUE(m.count(north)) << ctx << ": rank " << north << " missing"; + int n_south = get_int(m, north, "south"); + EXPECT_EQ(n_south, r) + << ctx << ": rank " << r << " north=" << north + << " but rank " << north << " south=" << n_south; + } + if (south >= 0) { + ASSERT_TRUE(m.count(south)) << ctx << ": rank " << south << " missing"; + int s_north = get_int(m, south, "north"); + EXPECT_EQ(s_north, r) + << ctx << ": rank " << r << " south=" << south + << " but rank " << south << " north=" << s_north; + } + if (west >= 0) { + ASSERT_TRUE(m.count(west)) << ctx << ": rank " << west << " missing"; + int w_east = get_int(m, west, "east"); + EXPECT_EQ(w_east, r) + << ctx << ": rank " << r << " west=" << west + << " but rank " << west << " east=" << w_east; + } + if (east >= 0) { + ASSERT_TRUE(m.count(east)) << ctx << ": rank " << east << " missing"; + int e_west = get_int(m, east, "west"); + EXPECT_EQ(e_west, r) + << ctx << ": rank " << r << " east=" << east + << " but rank " << east << " west=" << e_west; + } + } +} + +/* ── tests ──────────────────────────────────────────────────────────────────── */ + +class StencilTest : public ::testing::Test { +protected: + static constexpr int N = 8; +}; + +TEST_F(StencilTest, Coverage) { + auto m = run_debug("stencil_2d_nb", N); + check_coverage(m, N, "stencil default"); +} +TEST_F(StencilTest, Nprocs) { + auto m = run_debug("stencil_2d_nb", N); + check_nprocs(m, N, "stencil default"); +} +TEST_F(StencilTest, DataIntegrity) { + auto m = run_debug("stencil_2d_nb", N); + check_coverage(m, N, "stencil default"); + check_all_ok(m, "stencil default"); +} +TEST_F(StencilTest, Consistency) { + auto m = run_debug("stencil_2d_nb", N); + check_coverage(m, N, "stencil default"); + check_consistency(m, "stencil default"); +} +TEST_F(StencilTest, DataIntegrity_periodic) { + auto m = run_debug("stencil_2d_nb", N, "-periodic"); + check_coverage(m, N, "stencil periodic"); + check_all_ok(m, "stencil periodic"); +} +TEST_F(StencilTest, Consistency_periodic) { + auto m = run_debug("stencil_2d_nb", N, "-periodic"); + check_coverage(m, N, "stencil periodic"); + check_consistency(m, "stencil periodic"); +} +TEST_F(StencilTest, DataIntegrity_burst) { + auto m = run_debug("stencil_2d_nb", N, "-blength 0.001"); + check_coverage(m, N, "stencil burst"); + check_all_ok(m, "stencil burst"); +} +TEST_F(StencilTest, Consistency_burst) { + auto m = run_debug("stencil_2d_nb", N, "-blength 0.001"); + check_coverage(m, N, "stencil burst"); + check_consistency(m, "stencil burst"); +} +/* + * Periodic vs non-periodic distinction: non-periodic must produce at least + * one boundary rank with an MPI_PROC_NULL (negative) neighbour, and periodic + * must produce none. A silently-ignored -periodic flag would fail this. + */ +TEST_F(StencilTest, PeriodicityDistinct) { + auto plain = run_debug("stencil_2d_nb", N); + auto periodic = run_debug("stencil_2d_nb", N, "-periodic"); + + int plain_nulls = 0, periodic_nulls = 0; + for (const auto& [r, fields] : plain) { + for (const char *d : {"north","south","west","east"}) + if (get_int(plain, r, d) < 0) plain_nulls++; + } + for (const auto& [r, fields] : periodic) { + for (const char *d : {"north","south","west","east"}) + if (get_int(periodic, r, d) < 0) periodic_nulls++; + } + EXPECT_GT(plain_nulls, 0) + << "non-periodic stencil should have at least one boundary neighbour=MPI_PROC_NULL"; + EXPECT_EQ(periodic_nulls, 0) + << "periodic stencil should have no MPI_PROC_NULL neighbours (toroidal grid), " + << "got " << periodic_nulls << " — is -periodic silently ignored?"; +} From ad45df4f5f17cefcd472e70829311effa459327f Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Sat, 6 Jun 2026 19:32:44 +0200 Subject: [PATCH 09/26] [ADD] New tests. --- README.md | 10 ++++ src/allgather/allgather_comm_only.cpp | 13 ++++-- src/allgather/allgather_nb.c | 6 ++- src/allreduce/allreduce_b.c | 7 ++- src/allreduce/allreduce_nb.c | 20 ++++---- src/alltoall/alltoall_comm_only.cpp | 6 +++ src/alltoall/alltoall_man.c | 6 ++- src/alltoall/alltoall_nb.c | 6 ++- src/common.h | 64 +++++++++++++++----------- src/gather/gather_nb.c | 9 +++- src/incast/incast_nb.c | 6 ++- src/kpartners/kpartners_nb.c | 3 +- src/pairwise/pairwise_b.c | 6 +-- src/pairwise/pairwise_bsnbr.c | 15 ++---- src/pairwise/pairwise_nb.c | 6 +-- src/pingpong/pingpong_pairwise_b.c | 9 ---- src/reduce/reduce_b.c | 7 ++- src/reduce/reduce_nb.c | 13 +++--- src/reduce_scatter/reduce_scatter_b.c | 7 ++- src/reduce_scatter/reduce_scatter_nb.c | 13 +++--- src/ring/ring_bsnbr.c | 1 + src/ring/ring_nb.c | 1 + src/scatter/scatter_nb.c | 6 ++- src/stencil/stencil_2d_nb.c | 3 +- src/tools/checker.c | 8 ++-- tests/test_collectives.cpp | 16 +++++++ tests/test_incast.cpp | 24 ++++++++++ tests/test_misc.cpp | 34 ++++++++++++++ 28 files changed, 215 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index 0e95bda4a..ee58bb786 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,16 @@ The sampler implementations can be verified independently: mpirun -n 1 build/bin/dist_test ``` +## Auxiliary tools + +Besides the benchmarks, `src/tools/` builds a few helper binaries: + +| Binary | Purpose | +|--------|---------| +| `dist_test` | Verifies the burst-distribution samplers against their theoretical CDFs (ASCII histogram + mean / variance checks + KS test). Run single-rank: `mpirun -n 1 build/bin/dist_test`. | +| `checker` | An all-to-all workload (like `alltoall_b`) that additionally writes a per-iteration wall-clock timestamp log on the master rank (`checker_.log`, local time) — useful for correlating throughput dips with external system activity over a long run. Accepts the common flags. | +| `null_dummy` | Bare `MPI_Init` / `MPI_Finalize` with no communication; a baseline for measuring MPI startup / teardown overhead. | + ## Benchmarks Benchmarks are grouped by traffic pattern. The suffix convention is: diff --git a/src/allgather/allgather_comm_only.cpp b/src/allgather/allgather_comm_only.cpp index a405e87b2..77aae8038 100644 --- a/src/allgather/allgather_comm_only.cpp +++ b/src/allgather/allgather_comm_only.cpp @@ -119,10 +119,9 @@ int main(int argc, char** argv){ int *recv_buf; if(msg_size%sizeof(int)!=0){ - if(my_rank==master_rank){ - fprintf(stderr, "Msg-size (%d) must be divisible by size of int (%ld)",msg_size,sizeof(int)); - MPI_Abort(MPI_COMM_WORLD, -1); - } + if(my_rank==master_rank) + fprintf(stderr, "Msg-size (%d) must be divisible by size of int (%zu)\n",msg_size,sizeof(int)); + MPI_Abort(MPI_COMM_WORLD, -1); } /* -msgsize is the per-rank contribution (matches allgather_b/nb); the full @@ -177,6 +176,12 @@ int main(int argc, char** argv){ burst_start_time=MPI_Wtime(); do{ MPI_Barrier(MPI_COMM_WORLD); + /* comm_only metric: accumulate only the pure inter-rank transfer + * time across the granularity batch. The local self-copy + * (allgather_memcpy) is intentionally outside the timed region, so + * this isolates communication. This is a deliberately different + * measurement than the _b/_nb variants, which time the whole + * batched window as a single sample. */ measure_total_time=0.0; for(i=0;i 1). */ + recv_buf_size=(size_t)measure_granularity*w_size*msg_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -92,7 +94,7 @@ int main(int argc, char** argv){ MPI_Barrier(MPI_COMM_WORLD); measure_start_time=MPI_Wtime(); for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); diff --git a/src/allreduce/allreduce_b.c b/src/allreduce/allreduce_b.c index 3dfc71eb0..7f0008060 100644 --- a/src/allreduce/allreduce_b.c +++ b/src/allreduce/allreduce_b.c @@ -43,10 +43,9 @@ int main(int argc, char** argv){ int *recv_buf; if(msg_size%sizeof(int)!=0){ - if(my_rank==master_rank){ - fprintf(stderr, "Msg-size (%d) must be divisible by size of int (%ld)",msg_size,sizeof(int)); - MPI_Abort(MPI_COMM_WORLD, -1); - } + if(my_rank==master_rank) + fprintf(stderr, "Msg-size (%d) must be divisible by size of int (%zu)\n",msg_size,sizeof(int)); + MPI_Abort(MPI_COMM_WORLD, -1); } msg_size_ints=msg_size/sizeof(int); diff --git a/src/allreduce/allreduce_nb.c b/src/allreduce/allreduce_nb.c index 73824b261..6615ce2d7 100644 --- a/src/allreduce/allreduce_nb.c +++ b/src/allreduce/allreduce_nb.c @@ -38,24 +38,24 @@ int main(int argc, char** argv){ /*allocate buffers*/ int msg_size_ints; - int send_buf_size, recv_buf_size; + int send_buf_size; int *send_buf; int *recv_buf; MPI_Request *requests; if(msg_size%sizeof(int)!=0){ - if(my_rank==master_rank){ - fprintf(stderr, "Msg-size (%d) must be divisible by size of int (%ld)",msg_size,sizeof(int)); - MPI_Abort(MPI_COMM_WORLD, -1); - } + if(my_rank==master_rank) + fprintf(stderr, "Msg-size (%d) must be divisible by size of int (%zu)\n",msg_size,sizeof(int)); + MPI_Abort(MPI_COMM_WORLD, -1); } - + msg_size_ints=msg_size/sizeof(int); send_buf_size=msg_size; - recv_buf_size=msg_size; - + send_buf=(int*)malloc_align(send_buf_size); - recv_buf=(int*)malloc_align(recv_buf_size); + /* one reduced result per batched (granularity) call so the concurrently + * outstanding MPI_Iallreduce ops never share a receive buffer (-grty > 1). */ + recv_buf=(int*)malloc_align((size_t)measure_granularity*msg_size); durations=(double *)malloc_align(sizeof(double)*max_samples); requests=(MPI_Request*)malloc_align(sizeof(MPI_Request)*measure_granularity); @@ -102,7 +102,7 @@ int main(int argc, char** argv){ MPI_Barrier(MPI_COMM_WORLD); measure_start_time=MPI_Wtime(); for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); diff --git a/src/alltoall/alltoall_comm_only.cpp b/src/alltoall/alltoall_comm_only.cpp index c866742df..384a22cd6 100644 --- a/src/alltoall/alltoall_comm_only.cpp +++ b/src/alltoall/alltoall_comm_only.cpp @@ -145,6 +145,12 @@ int main(int argc, char** argv){ burst_start_time=MPI_Wtime(); do{ MPI_Barrier(MPI_COMM_WORLD); + /* comm_only metric: accumulate only the pure inter-rank transfer + * time across the granularity batch. The local self-copy + * (all2all_memcpy) is intentionally outside the timed region, so + * this isolates communication. This is a deliberately different + * measurement than the _b/_nb variants, which time the whole + * batched window as a single sample. */ measure_total_time=0.0; for(i=0;i 1). */ + recv_buf_size=(size_t)measure_granularity*w_size*msg_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -95,7 +97,7 @@ int main(int argc, char** argv){ measure_start_time=MPI_Wtime(); for(i=0;i 1). */ + recv_buf_size=(size_t)measure_granularity*msg_size*w_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -92,7 +94,7 @@ int main(int argc, char** argv){ MPI_Barrier(MPI_COMM_WORLD); measure_start_time=MPI_Wtime(); for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); diff --git a/src/common.h b/src/common.h index eb092037d..cf8e8a158 100644 --- a/src/common.h +++ b/src/common.h @@ -13,7 +13,7 @@ /* Exponential: shape parameter accepted for API uniformity but not used — * the distribution is fully determined by its mean. */ -static double rand_exp(double mean, double shape) +static inline double rand_exp(double mean, double shape) { (void)shape; double u = (rand() + 0.5) / (RAND_MAX + 1.0); /* shift away from 0 and 1 */ @@ -23,7 +23,7 @@ static double rand_exp(double mean, double shape) /* Pareto: shape = tail exponent α. Must be > 1 for a finite mean. * Parameterised so that E[X] = mean regardless of α. * Inverse-CDF method: x = x_m * u^(-1/α), u ~ Uniform(0,1). */ -static double rand_pareto(double mean, double shape) +static inline double rand_pareto(double mean, double shape) { double alpha = shape; if (alpha <= 1.0) { @@ -42,7 +42,7 @@ static double rand_pareto(double mean, double shape) /* Log-normal: shape = σ of the underlying normal. * Parameterised so that E[X] = mean regardless of σ. * Box-Muller transform. */ -static double rand_lognormal(double mean, double sigma) +static inline double rand_lognormal(double mean, double sigma) { if (mean <= 0.0) { fprintf(stderr, "rand_lognormal: mean must be > 0, got %g\n", mean); @@ -60,7 +60,7 @@ static double rand_lognormal(double mean, double sigma) } /* Dispatch to the selected sampler. */ -static double rand_duration(double mean, const char *dist, double shape) +static inline double rand_duration(double mean, const char *dist, double shape) { if (strcmp(dist, "pareto") == 0) return rand_pareto(mean, shape); if (strcmp(dist, "lognormal") == 0) return rand_lognormal(mean, shape); @@ -68,7 +68,7 @@ static double rand_duration(double mean, const char *dist, double shape) } /*sleep seconds given as double*/ -static int dsleep(double t) +static inline int dsleep(double t) { struct timespec t1, t2; t1.tv_sec = (long)t; @@ -77,7 +77,7 @@ static int dsleep(double t) } /*double comparison function for quicksort*/ -static int compare_doubles(const void *p1, const void *p2) +static inline int compare_doubles(const void *p1, const void *p2) { if (*(double *)p1 < *(double *)p2) return -1; @@ -91,8 +91,8 @@ static int compare_doubles(const void *p1, const void *p2) static int my_rank; static int w_size; static int master_rank = 0; -static int curr_iters; /* total outer iterations executed (warmup + measured) */ -static int measured_iters; /* number of recorded samples (excludes warmup) */ +static long long curr_iters; /* total outer iterations executed (warmup + measured) */ +static long long measured_iters; /* number of recorded samples (excludes warmup) */ static int warm_up_iters = 5; static int max_samples = 1000; static double *durations; @@ -127,7 +127,7 @@ static double pause_shape = 1.5; /* Bounds-checked accessor for a flag's value argument. Aborts with a clear * message instead of dereferencing argv[argc] (== NULL) when a value-taking * flag is supplied as the final command-line token. */ -static const char *arg_value(int argc, char **argv, int *i) +static inline const char *arg_value(int argc, char **argv, int *i) { if (*i + 1 >= argc) { if (my_rank == master_rank) @@ -140,7 +140,7 @@ static const char *arg_value(int argc, char **argv, int *i) /* Validate a distribution name / shape pair selected via -bldist/-bpdist so a * misconfiguration fails loudly at startup rather than silently producing * garbage durations later (e.g. log-normal with a non-positive mean). */ -static void validate_burst_dist(const char *what, const char *dist, +static inline void validate_burst_dist(const char *what, const char *dist, double mean, double shape) { if (mean <= 0.0) { @@ -167,7 +167,7 @@ static void validate_burst_dist(const char *what, const char *dist, * the new argc is returned, so each benchmark can do a second pass for its * own flags. Also seeds the RNG and resolves -mrand after parsing. */ -static int parse_common_args(int argc, char **argv) +static inline int parse_common_args(int argc, char **argv) { int new_argc = 1; /* always keep argv[0] (program name) */ int do_rand_master = 0; @@ -222,12 +222,22 @@ static int parse_common_args(int argc, char **argv) if (do_rand_master) master_rank = rand() % w_size; + /* validate the resolved master rank is a real rank. An out-of-range -mrank + * would otherwise become an invalid root in MPI_Gather/Bcast/Reduce, and can + * deadlock benchmarks that branch on (my_rank == master_rank). Gate the + * message on rank 0 because master_rank itself may be the bad value. */ + if (master_rank < 0 || master_rank >= w_size) { + if (my_rank == 0) + fprintf(stderr, "-mrank must be in [0, %d), got %d\n", w_size, master_rank); + MPI_Abort(MPI_COMM_WORLD, -1); + } + return new_argc; } /* Convenience wrappers used by benchmark measurement loops. */ -static double sample_burst_length(double mean) { return rand_duration(mean, burst_dist, burst_shape); } -static double sample_pause_length(double mean) { return rand_duration(mean, pause_dist, pause_shape); } +static inline double sample_burst_length(double mean) { return rand_duration(mean, burst_dist, burst_shape); } +static inline double sample_pause_length(double mean) { return rand_duration(mean, pause_dist, pause_shape); } /* Record a single measured-iteration latency into the ring buffer. * Skips writes during warm-up: callers gate this by `if (k >= warm_up_iters)` @@ -246,7 +256,7 @@ static inline void record_duration(double t) /*format a duration (seconds) with auto-scaled units; "%9.2f UU" is normally * 12 chars wide (9 is a minimum field width, so extreme outliers can exceed it)*/ -static void format_duration(char *buf, size_t len, double t) +static inline void format_duration(char *buf, size_t len, double t) { if (t < 1e-3) snprintf(buf, len, "%9.2f us", t * 1e6); @@ -256,7 +266,7 @@ static void format_duration(char *buf, size_t len, double t) snprintf(buf, len, "%9.2f s", t); } -static void write_results() +static inline void write_results() { double duration_sum; double duration_median; @@ -268,11 +278,11 @@ static void write_results() if (measured_iters > max_samples) /* wrapped the sampling ring buffer */ { num_samples = max_samples; - start_index = measured_iters % max_samples; + start_index = (int)(measured_iters % max_samples); } else { - num_samples = measured_iters; + num_samples = (int)measured_iters; start_index = 0; } @@ -357,9 +367,9 @@ static void write_results() printf("\033[2m" " -------------------------------------------------------------" "\033[0m\n"); - printf(" %d samples · %d iterations total\n", num_samples, curr_iters); + printf(" %d samples · %lld iterations total\n", num_samples, curr_iters); } else { - printf("Ran %d iterations. Measured %d iterations.\n", curr_iters, num_samples); + printf("Ran %lld iterations. Measured %d iterations.\n", curr_iters, num_samples); } fflush(stdout); } @@ -372,7 +382,7 @@ static void write_results() /* Async-signal-safe handler: only sets a flag. The measurement loop in each * benchmark observes the flag and exits cleanly; write_results() and * MPI_Finalize() happen on the main thread, never from signal context. */ -static void sig_handler(int sig) +static inline void sig_handler(int sig) { (void)sig; shutdown_requested = 1; @@ -380,7 +390,7 @@ static void sig_handler(int sig) /* Install the SIGUSR1 shutdown handler with sigaction() rather than signal(), * for portable, persistent (non-one-shot) semantics across platforms. */ -static void install_shutdown_handler(void) +static inline void install_shutdown_handler(void) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); @@ -397,7 +407,7 @@ static void install_shutdown_handler(void) * (called once per outer iteration, OUTSIDE the timed region) guarantees every * rank breaks on the same iteration. Returns non-zero if any rank requested * shutdown. */ -static int check_shutdown(void) +static inline int check_shutdown(void) { int local = (int)shutdown_requested; int global = 0; @@ -408,7 +418,7 @@ static int check_shutdown(void) /* ── combinatorics helpers ───────────────────────────────────────────────── */ /*use Fisher-Yates to permute array*/ -static void permute(int *a, int n) +static inline void permute(int *a, int n) { int j, t, i; for (i = n; i > 1; i--) @@ -421,7 +431,7 @@ static void permute(int *a, int n) } /*mathematical mod without negative numbers*/ -static int mod(int a, int b) +static inline int mod(int a, int b) { int c = a % b; if (c < 0) @@ -436,7 +446,7 @@ static int mod(int a, int b) * * For odd n the last element targets itself (it has no partner). */ -static void random_pairs(int *a, int n) +static inline void random_pairs(int *a, int n) { int i; int has_self = (n % 2 == 1); @@ -479,7 +489,7 @@ static void random_pairs(int *a, int n) * leaves ranks 8..11 self-paired). Full disjoint tiling is only guaranteed when * 2*o divides n (notably o=1). Recommended usage: 1 <= o <= n/2. */ -static void offset_pairs(int *a, int n, int o) +static inline void offset_pairs(int *a, int n, int o) { int i, t; for (i = 0; i < n; i++) @@ -504,7 +514,7 @@ static void offset_pairs(int *a, int n, int o) } #define ALIGNMENT (sysconf(_SC_PAGESIZE)) -static void* malloc_align(size_t size) +static inline void* malloc_align(size_t size) { void *p = NULL; int ret = posix_memalign(&p, ALIGNMENT, size); diff --git a/src/gather/gather_nb.c b/src/gather/gather_nb.c index dc5013441..0b8ffaabc 100644 --- a/src/gather/gather_nb.c +++ b/src/gather/gather_nb.c @@ -47,7 +47,9 @@ int main(int argc, char** argv){ requests=(MPI_Request*)malloc_align(sizeof(MPI_Request)*measure_granularity); if(my_rank==master_rank){ - recv_buf=(unsigned char*)malloc_align((size_t)w_size*msg_size); + /* one full gathered block per batched (granularity) call so the + * concurrently outstanding MPI_Igather ops never share a buffer. */ + recv_buf=(unsigned char*)malloc_align((size_t)measure_granularity*w_size*msg_size); if(recv_buf==NULL){ fprintf(stderr,"Failed to allocate recv_buf on rank %d\n",my_rank); MPI_Abort(MPI_COMM_WORLD, -1); @@ -96,7 +98,10 @@ int main(int argc, char** argv){ MPI_Barrier(MPI_COMM_WORLD); measure_start_time=MPI_Wtime(); for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); diff --git a/src/incast/incast_nb.c b/src/incast/incast_nb.c index fb10d7e54..f0ae51170 100644 --- a/src/incast/incast_nb.c +++ b/src/incast/incast_nb.c @@ -44,7 +44,9 @@ int main(int argc, char** argv){ MPI_Request *recv_requests; send_buf_size=msg_size; - recv_buf_size=(size_t)(w_size-1)*msg_size; + /* one slot per (granularity, sender) pair so the concurrently outstanding + * MPI_Irecv ops at the root never share a receive buffer (-grty > 1). */ + recv_buf_size=(size_t)measure_granularity*(w_size-1)*msg_size; send_buf=(unsigned char*)malloc_align(send_buf_size); recv_buf=(unsigned char*)malloc_align(recv_buf_size); @@ -98,7 +100,7 @@ int main(int argc, char** argv){ for(i=0;i 1). */ + recv_buf=(int*)malloc_align((size_t)measure_granularity*msg_size); durations=(double *)malloc_align(sizeof(double)*max_samples); requests=(MPI_Request*)malloc_align(sizeof(MPI_Request)*measure_granularity); @@ -98,7 +99,7 @@ int main(int argc, char** argv){ MPI_Barrier(MPI_COMM_WORLD); measure_start_time=MPI_Wtime(); for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); diff --git a/src/reduce_scatter/reduce_scatter_b.c b/src/reduce_scatter/reduce_scatter_b.c index ad61ac071..da98b6f4c 100644 --- a/src/reduce_scatter/reduce_scatter_b.c +++ b/src/reduce_scatter/reduce_scatter_b.c @@ -43,10 +43,9 @@ int main(int argc, char** argv){ int *recvcounts; if(msg_size%sizeof(int)!=0){ - if(my_rank==master_rank){ - fprintf(stderr, "Msg-size (%d) must be divisible by size of int (%ld)",msg_size,sizeof(int)); - MPI_Abort(MPI_COMM_WORLD, -1); - } + if(my_rank==master_rank) + fprintf(stderr, "Msg-size (%d) must be divisible by size of int (%zu)\n",msg_size,sizeof(int)); + MPI_Abort(MPI_COMM_WORLD, -1); } msg_size_ints=msg_size/sizeof(int); diff --git a/src/reduce_scatter/reduce_scatter_nb.c b/src/reduce_scatter/reduce_scatter_nb.c index 85b8b8fc8..505ecf8d4 100644 --- a/src/reduce_scatter/reduce_scatter_nb.c +++ b/src/reduce_scatter/reduce_scatter_nb.c @@ -44,17 +44,18 @@ int main(int argc, char** argv){ MPI_Request *requests; if(msg_size%sizeof(int)!=0){ - if(my_rank==master_rank){ - fprintf(stderr, "Msg-size (%d) must be divisible by size of int (%ld)",msg_size,sizeof(int)); - MPI_Abort(MPI_COMM_WORLD, -1); - } + if(my_rank==master_rank) + fprintf(stderr, "Msg-size (%d) must be divisible by size of int (%zu)\n",msg_size,sizeof(int)); + MPI_Abort(MPI_COMM_WORLD, -1); } msg_size_ints=msg_size/sizeof(int); /*each rank sends w_size*msg_size total, receives msg_size*/ send_buf=(int*)malloc_align((size_t)w_size*msg_size); - recv_buf=(int*)malloc_align(msg_size); + /* one received chunk per batched (granularity) call so the concurrently + * outstanding MPI_Ireduce_scatter ops never share a receive buffer (-grty > 1). */ + recv_buf=(int*)malloc_align((size_t)measure_granularity*msg_size); recvcounts=(int*)malloc_align(sizeof(int)*w_size); durations=(double *)malloc_align(sizeof(double)*max_samples); requests=(MPI_Request*)malloc_align(sizeof(MPI_Request)*measure_granularity); @@ -106,7 +107,7 @@ int main(int argc, char** argv){ MPI_Barrier(MPI_COMM_WORLD); measure_start_time=MPI_Wtime(); for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); diff --git a/src/ring/ring_bsnbr.c b/src/ring/ring_bsnbr.c index bbff81c54..103e4baee 100644 --- a/src/ring/ring_bsnbr.c +++ b/src/ring/ring_bsnbr.c @@ -123,6 +123,7 @@ int main(int argc, char** argv){ burst_start_time=MPI_Wtime(); do{ MPI_Barrier(MPI_COMM_WORLD); + antideadlock_tag=0; /* reset per timed window so the tag never grows unbounded (stays < MPI_TAG_UB on long/endless runs) */ measure_start_time=MPI_Wtime(); for(i=0;i 1). */ + recv_buf=(unsigned char*)malloc_align((size_t)measure_granularity*msg_size); durations=(double *)malloc_align(sizeof(double)*max_samples); requests=(MPI_Request*)malloc_align(sizeof(MPI_Request)*measure_granularity); @@ -94,7 +96,7 @@ int main(int argc, char** argv){ MPI_Barrier(MPI_COMM_WORLD); measure_start_time=MPI_Wtime(); for(i=0;i= warm_up_iters) record_duration(MPI_Wtime()-measure_start_time); diff --git a/src/stencil/stencil_2d_nb.c b/src/stencil/stencil_2d_nb.c index 820330aa3..d75fdfcca 100644 --- a/src/stencil/stencil_2d_nb.c +++ b/src/stencil/stencil_2d_nb.c @@ -28,7 +28,7 @@ int main(int argc, char** argv){ argc = parse_common_args(argc, argv); for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-dimx") == 0) { - dimx = atoi(argv[++i]); + dimx = atoi(arg_value(argc, argv, &i)); } else if (strcmp(argv[i], "-periodic") == 0) { periodic = true; } else { @@ -139,6 +139,7 @@ int main(int argc, char** argv){ burst_start_time=MPI_Wtime(); do{ MPI_Barrier(MPI_COMM_WORLD); + antideadlock_tag=0; /* reset per timed window so the tag never grows unbounded (stays < MPI_TAG_UB on long/endless runs) */ measure_start_time=MPI_Wtime(); for(i=0;itm_hour , time_str_tm->tm_min , time_str_tm->tm_sec diff --git a/tests/test_collectives.cpp b/tests/test_collectives.cpp index 10f4c79f6..3b7205b73 100644 --- a/tests/test_collectives.cpp +++ b/tests/test_collectives.cpp @@ -74,6 +74,15 @@ TEST_P(UnrootedCollectiveTest, DataIntegrity_burst) { check_coverage(m, N, GetParam() + " burst"); check_all_ok(m, GetParam() + " burst"); } +/* Granularity batching (-grty > 1): the _nb / manual variants issue grty + * concurrent operations per timed window. Each must write a DISJOINT receive + * slot — a shared buffer would be an MPI overlap violation that can corrupt + * data. This exercises that path (default -grty 1 would never catch it). */ +TEST_P(UnrootedCollectiveTest, DataIntegrity_grty) { + auto m = run_debug(GetParam(), N, "-grty 4"); + check_coverage(m, N, GetParam() + " grty=4"); + check_all_ok(m, GetParam() + " grty=4"); +} INSTANTIATE_TEST_SUITE_P( Collectives, UnrootedCollectiveTest, @@ -117,6 +126,13 @@ TEST_P(RootedCollectiveTest, DataIntegrity_burst) { check_coverage(m, N, GetParam() + " burst"); check_all_ok(m, GetParam() + " burst"); } +/* Granularity batching (-grty > 1): grty concurrent rooted ops per window, + * each into a disjoint receive slot. Guards the buffer-indexing fix. */ +TEST_P(RootedCollectiveTest, DataIntegrity_grty) { + auto m = run_debug(GetParam(), N, "-grty 4"); + check_coverage(m, N, GetParam() + " grty=4"); + check_all_ok(m, GetParam() + " grty=4"); +} TEST_P(RootedCollectiveTest, NonDefaultRoot) { auto m = run_debug(GetParam(), N, "-mrank " + std::to_string(ALT)); check_coverage(m, N, GetParam() + " mrank=3"); diff --git a/tests/test_incast.cpp b/tests/test_incast.cpp index 362f2139b..704c97158 100644 --- a/tests/test_incast.cpp +++ b/tests/test_incast.cpp @@ -117,6 +117,14 @@ TEST_F(IncastNbTest, Burst) { check_roles(m, N, MASTER, "incast_nb burst"); check_all_ok(m, "incast_nb burst"); } +/* -grty>1: the root posts grty*(N-1) concurrent Irecvs, each into a disjoint + * slot. A shared slot would be an MPI overlap violation; this guards it. */ +TEST_F(IncastNbTest, Grty) { + auto m = run_debug("incast_nb", N, "-grty 4"); + check_coverage(m, N, "incast_nb grty=4"); + check_roles(m, N, MASTER, "incast_nb grty=4"); + check_all_ok(m, "incast_nb grty=4"); +} TEST_F(IncastNbTest, NonDefaultMaster) { const int ALT = 3; auto m = run_debug("incast_nb", N, "-mrank " + std::to_string(ALT)); @@ -207,6 +215,14 @@ TEST_F(IncastGetTest, Burst) { check_roles(m, N, MASTER, "incast_get burst"); check_all_ok(m, "incast_get burst"); } +/* -grty>1: the root issues grty Gets per sender into distinct window-offset + * slots; guards the RMA displacement math (j*M*grty + i*M). */ +TEST_F(IncastGetTest, Grty) { + auto m = run_debug("incast_get", N, "-grty 4"); + check_coverage(m, N, "incast_get grty=4"); + check_roles(m, N, MASTER, "incast_get grty=4"); + check_all_ok(m, "incast_get grty=4"); +} TEST_F(IncastGetTest, NonDefaultMaster) { const int ALT = 3; auto m = run_debug("incast_get", N, "-mrank " + std::to_string(ALT)); @@ -252,6 +268,14 @@ TEST_F(IncastPutTest, Burst) { check_roles(m, N, MASTER, "incast_put burst"); check_all_ok(m, "incast_put burst"); } +/* -grty>1: each sender issues grty Puts into distinct window-offset slots; + * guards the RMA displacement math (rank*M*grty + i*M). */ +TEST_F(IncastPutTest, Grty) { + auto m = run_debug("incast_put", N, "-grty 4"); + check_coverage(m, N, "incast_put grty=4"); + check_roles(m, N, MASTER, "incast_put grty=4"); + check_all_ok(m, "incast_put grty=4"); +} TEST_F(IncastPutTest, NonDefaultMaster) { const int ALT = 3; auto m = run_debug("incast_put", N, "-mrank " + std::to_string(ALT)); diff --git a/tests/test_misc.cpp b/tests/test_misc.cpp index 043851c2c..f3bcd1ac4 100644 --- a/tests/test_misc.cpp +++ b/tests/test_misc.cpp @@ -12,6 +12,7 @@ #include #include #include +#include /* std::count */ #include "popen_helpers.h" using blink::run_debug_capture; @@ -206,3 +207,36 @@ TEST(Shutdown, Sigusr1ToSingleRankShutsDownCleanly) EXPECT_NE(bench.find("Measured"), std::string::npos) << "no results footer after clean shutdown.\nbenchmark output:\n" << bench; } + +/* ── CLI validation guards ──────────────────────────────────────────────────── */ + +/* + * An out-of-range -mrank must fail loudly (clean MPI_Abort with a diagnostic), + * never crash or run with an invalid collective root. Regression guard for the + * master_rank range check added to parse_common_args(). + */ +TEST(CliGuards, OutOfRangeMrankAbortsCleanly) +{ + DebugRun r = run_debug_capture("alltoall_b", 8, "-mrank 99"); + EXPECT_FALSE(r.normal_exit && r.exit_code == 0) + << "out-of-range -mrank should abort, but the run exited 0.\n" + << "stdout:\n" << r.stdout_raw; + EXPECT_NE(r.stderr_raw.find("mrank must be in"), std::string::npos) + << "expected an -mrank range diagnostic on stderr.\nstderr:\n" << r.stderr_raw; +} + +/* + * A value-taking flag supplied as the final token must be rejected with a + * "Missing value" message rather than dereferencing argv[argc] (== NULL). + * Regression guard for arg_value() and the benchmark-specific parsers that + * now route through it. + */ +TEST(CliGuards, MissingFlagValueAbortsCleanly) +{ + DebugRun r = run_debug_capture("pairwise_b", 8, "-mode"); + EXPECT_FALSE(r.normal_exit && r.exit_code == 0) + << "missing -mode value should abort, but the run exited 0.\n" + << "stdout:\n" << r.stdout_raw; + EXPECT_NE(r.stderr_raw.find("Missing value for option -mode"), std::string::npos) + << "expected a 'Missing value' diagnostic on stderr.\nstderr:\n" << r.stderr_raw; +} From fa5405540417a1981f4450c95b91d448349e192f Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Sat, 6 Jun 2026 20:03:06 +0200 Subject: [PATCH 10/26] Add GitHub Actions CI with ctest + gcovr/Codecov coverage Workflow (.github/workflows/ci.yml) builds every benchmark and the test suite on Ubuntu with OpenMPI and runs ctest on each push and pull request; MPI oversubscribe is enabled so the 8-rank suites run on smaller runners. Adds a BLINK_COVERAGE CMake option (-O0 --coverage -fprofile-update=atomic so concurrent MPI ranks do not corrupt shared .gcda files); CI runs gcovr over src/, prints it to the job summary, uploads a coverage.xml artifact, and best-effort uploads to Codecov (continue-on-error, so a missing token never fails CI). codecov.yml marks coverage informational. README: add CI and Codecov status badges plus a Continuous integration section documenting the flow and the one-time Codecov token setup. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 76 ++++++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 11 +++++- README.md | 30 ++++++++++++++++ codecov.yml | 11 ++++++ 4 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml create mode 100644 codecov.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..c70c2cd3a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,76 @@ +name: CI + +# Run on every push and pull request so each commit is validated. +on: + push: + pull_request: + +# Cancel an in-progress run if a newer commit is pushed to the same ref. +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: build • test • coverage (OpenMPI) + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + # GitHub-hosted runners have fewer cores than the 8 ranks some suites + # launch, so allow MPI to oversubscribe. (rmaps_base for OpenMPI 4.x, + # the PRTE policy for OpenMPI 5.x — setting the unused one is harmless.) + OMPI_MCA_rmaps_base_oversubscribe: "1" + PRTE_MCA_rmaps_default_mapping_policy: ":oversubscribe" + steps: + - uses: actions/checkout@v4 + + - name: Install toolchain (CMake, OpenMPI, gcovr) + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + cmake build-essential openmpi-bin libopenmpi-dev gcovr + + - name: Configure (tests + coverage) + run: cmake -B build -DBLINK_TESTS=ON -DBLINK_COVERAGE=ON + + - name: Build + run: cmake --build build -j"$(nproc)" + + - name: Run tests + run: ctest --test-dir build --output-on-failure --no-tests=error + + # ---- coverage (best-effort: never flips the test verdict) ---- + - name: Coverage report (gcovr) + if: always() + continue-on-error: true + run: | + gcovr --root . --filter 'src/' \ + --xml-pretty -o coverage.xml \ + --print-summary | tee coverage_summary.txt + { + echo '## Coverage (src/)' + echo '```' + cat coverage_summary.txt + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload coverage to Codecov + if: always() + continue-on-error: true + uses: codecov/codecov-action@v4 + with: + files: coverage.xml + fail_ci_if_error: false + env: + # Optional: set this repo secret to enable Codecov uploads/badge. + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload coverage artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: | + coverage.xml + coverage_summary.txt + if-no-files-found: ignore diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e328f8a2..2ddd6887c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,16 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) endif() -add_compile_options(-O3) +option(BLINK_COVERAGE "Build with gcov coverage instrumentation (-O0 --coverage)" OFF) +if(BLINK_COVERAGE) + # -O0 for accurate line attribution; atomic counter updates so the multiple + # concurrent MPI ranks of a single benchmark binary don't corrupt the shared + # .gcda files when they dump coverage on exit. + add_compile_options(-O0 -g --coverage -fprofile-update=atomic) + add_link_options(--coverage) +else() + add_compile_options(-O3) +endif() add_compile_definitions(_GNU_SOURCE) add_subdirectory(src) diff --git a/README.md b/README.md index ee58bb786..b0239fa68 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,13 @@ Blink logo +
+ +[![CI](https://github.com/DanieleDeSensi/blink/actions/workflows/ci.yml/badge.svg)](https://github.com/DanieleDeSensi/blink/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/DanieleDeSensi/blink/graph/badge.svg)](https://codecov.io/gh/DanieleDeSensi/blink) + +
+ Blink is a collection of MPI benchmarks designed for long-running, in-situ measurement of network behaviour under realistic traffic conditions. Each benchmark runs for a configurable number of iterations (or endlessly until interrupted), records per-iteration latency on every rank, and emits a CSV summary when it finishes — either naturally or via `SIGUSR1`. @@ -73,6 +80,29 @@ The test binaries bake in the MPI launcher paths at configure time. Override th BLINK_MPIEXEC=/opt/mpi/bin/mpirun BLINK_NPROC_FLAG=-np BLINK_BIN_DIR=/custom/bin ctest --test-dir build ``` +### Continuous integration + +Every push and pull request triggers the [CI workflow](.github/workflows/ci.yml): it +builds every benchmark and the test suite on Ubuntu with OpenMPI, runs `ctest`, and +produces a coverage report. The badges at the top of this file show the latest result. + +The build is instrumented via `-DBLINK_COVERAGE=ON` (`-O0 --coverage`, atomic counters +so concurrent MPI ranks don't corrupt the `.gcda` files); a [`gcovr`](https://gcovr.com) +summary of `src/` is printed to the run's job summary, attached as an artifact, and +uploaded to Codecov. To reproduce a coverage run locally: + +```bash +cmake -B build-cov -DBLINK_TESTS=ON -DBLINK_COVERAGE=ON +cmake --build build-cov -j$(nproc) +ctest --test-dir build-cov --output-on-failure +gcovr --root . --filter 'src/' --print-summary +``` + +> **Coverage badge setup (one-time):** the test badge works out of the box. To activate +> the coverage badge, sign in at with GitHub, enable this repository, +> and add a `CODECOV_TOKEN` repository secret (Settings → Secrets and variables → Actions). +> Until then CI still publishes the coverage summary in each run's job-summary and artifact. + ## Common flags diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..1ec9f8858 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,11 @@ +# Coverage is informational: it reports numbers but never fails a commit or PR. +# Only the test results (the CI "test" job) gate merges. Flip informational to +# false and set a target if you later want coverage regressions to block. +coverage: + status: + project: + default: + informational: true + patch: + default: + informational: true From c37f18c314ebd9ce802ae6f56873f2959c6901e8 Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Sat, 6 Jun 2026 20:13:00 +0200 Subject: [PATCH 11/26] docs: pin CI and coverage badges to the dev branch Co-Authored-By: Claude Opus 4.8 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b0239fa68..8c13c18d3 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@
-[![CI](https://github.com/DanieleDeSensi/blink/actions/workflows/ci.yml/badge.svg)](https://github.com/DanieleDeSensi/blink/actions/workflows/ci.yml) -[![codecov](https://codecov.io/gh/DanieleDeSensi/blink/graph/badge.svg)](https://codecov.io/gh/DanieleDeSensi/blink) +[![CI](https://github.com/DanieleDeSensi/blink/actions/workflows/ci.yml/badge.svg?branch=dev)](https://github.com/DanieleDeSensi/blink/actions/workflows/ci.yml?query=branch%3Adev) +[![codecov](https://codecov.io/gh/DanieleDeSensi/blink/branch/dev/graph/badge.svg)](https://codecov.io/gh/DanieleDeSensi/blink/tree/dev)
From 54be384de48485d00a9bd2115c6ad677fb1f66dc Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Sat, 6 Jun 2026 20:14:07 +0200 Subject: [PATCH 12/26] [ADD] Coverage. --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 8c13c18d3..d5f2a8be3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Blink logo -
+
[![CI](https://github.com/DanieleDeSensi/blink/actions/workflows/ci.yml/badge.svg?branch=dev)](https://github.com/DanieleDeSensi/blink/actions/workflows/ci.yml?query=branch%3Adev) [![codecov](https://codecov.io/gh/DanieleDeSensi/blink/branch/dev/graph/badge.svg)](https://codecov.io/gh/DanieleDeSensi/blink/tree/dev) @@ -98,12 +98,6 @@ ctest --test-dir build-cov --output-on-failure gcovr --root . --filter 'src/' --print-summary ``` -> **Coverage badge setup (one-time):** the test badge works out of the box. To activate -> the coverage badge, sign in at with GitHub, enable this repository, -> and add a `CODECOV_TOKEN` repository secret (Settings → Secrets and variables → Actions). -> Until then CI still publishes the coverage summary in each run's job-summary and artifact. - - ## Common flags Every benchmark understands the following flags: From e065eb9356123bf525eb28bfab1dcdcf6e2d3f58 Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Sat, 6 Jun 2026 20:16:58 +0200 Subject: [PATCH 13/26] docs: point badges at the canonical hlc-lab/blink repo Co-Authored-By: Claude Opus 4.8 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d5f2a8be3..4797ed55b 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@
-[![CI](https://github.com/DanieleDeSensi/blink/actions/workflows/ci.yml/badge.svg?branch=dev)](https://github.com/DanieleDeSensi/blink/actions/workflows/ci.yml?query=branch%3Adev) -[![codecov](https://codecov.io/gh/DanieleDeSensi/blink/branch/dev/graph/badge.svg)](https://codecov.io/gh/DanieleDeSensi/blink/tree/dev) +[![CI](https://github.com/hlc-lab/blink/actions/workflows/ci.yml/badge.svg?branch=dev)](https://github.com/hlc-lab/blink/actions/workflows/ci.yml?query=branch%3Adev) +[![codecov](https://codecov.io/gh/hlc-lab/blink/branch/dev/graph/badge.svg)](https://codecov.io/gh/hlc-lab/blink/tree/dev)
From f93ee109274d9bebd220d577137afaf0e977ea62 Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Sat, 6 Jun 2026 21:18:08 +0200 Subject: [PATCH 14/26] Add -plot: ASCII histogram of per-iteration latency distribution Implemented entirely in common.h, so every benchmark gets it for free. New flags: -plot (additive, printed after the CSV/table), -plotstat (avg|min|max|median|mainrank, default max), -plotbins, -plotbinsize (accepts a unit e.g. 2ms/500us, aligns bin edges to multiples of the width, caps at 64 bins folding the tail), and -plotlog (geometric bins). Includes a parse_duration() helper, startup validation, and a percentile (min/mean/p50/p90/p99/max) footer. Also: README flags + Output-format section, and test_misc smoke tests (render, stat/log/fixed selectors, invalid-stat and binsize+log conflict guards). Co-Authored-By: Claude Opus 4.8 --- README.md | 32 +++++++ src/common.h | 202 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_misc.cpp | 48 +++++++++++ 3 files changed, 282 insertions(+) diff --git a/README.md b/README.md index 4797ed55b..60930ba60 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,11 @@ Every benchmark understands the following flags: | `-bpdist ` | — | Randomise pause length using distribution `D` (`exp`, `pareto`, `lognormal`) | | `-bpshape ` | `1.5` | Shape parameter for pause distribution (α for Pareto, σ for log-normal) | | `-pretty-print` | off | Human-readable table output instead of CSV (see [Output format](#output-format)) | +| `-plot` | off | Append an ASCII histogram of the per-iteration timing distribution (see [Output format](#output-format)) | +| `-plotstat ` | `max` | Which cross-rank statistic to histogram each iteration: `avg`, `min`, `max`, `median`, `mainrank` | +| `-plotbins ` | `10` | Number of histogram bins | +| `-plotbinsize ` | — | Fixed bin width with optional unit (e.g. `2ms`, `500us`, `0.001`); overrides `-plotbins`, linear bins only | +| `-plotlog` | off | Use logarithmic (geometric) bins — good for heavy-tailed latencies | ### Burst distributions @@ -365,6 +370,33 @@ mpirun -n 8 build/bin/alltoall_nb -iter 5 -pretty-print The `MainRank` column is omitted in pretty mode. Pipe through a tool such as `sed 's/\x1b\[[0-9;]*m//g'` to strip ANSI colour codes if needed. +### Distribution plot + +Pass `-plot` to **append** an ASCII histogram of the per-iteration timing distribution (the CSV / pretty table is still printed first). Each iteration contributes one value — the cross-rank statistic chosen with `-plotstat` (default `max`, i.e. the slowest rank, which is the completion time of a collective). A percentile footer summarises the tail regardless of how the bins fall. + +```bash +mpirun -n 8 build/bin/alltoall_nb -iter 400 -plot -plotstat max +``` + +``` + Per-iteration latency · stat=max · n=400 · linear bins + ---------------------------------------------------------------------- + [ 6.05 us - 32.88 us] ████████████████████████████████████████ 97.8% + [ 32.88 us - 59.70 us] 1.0% + ... + [ 247.49 us - 274.31 us] 0.2% + ---------------------------------------------------------------------- + n=400 · min 6.05 us · mean 9.09 us · p50 6.62 us · p90 7.15 us · p99 74.71 us · max 274.31 us +``` + +Binning options: + +- `-plotbins ` — number of bins (default `10`); always produces a readable, fixed-height histogram. +- `-plotbinsize ` — fixed bin **width** with an optional unit (`2ms`, `500us`, `100ns`, or a bare number = seconds). Edges are aligned to multiples of the width so plots from different runs line up. Overrides `-plotbins`; capped at 64 bins (the tail folds into the last bin, with a note) so a tiny width can't flood the terminal. +- `-plotlog` — logarithmic (geometric) bins, which reveal the shape of heavy-tailed latency distributions far better than linear bins. Cannot be combined with `-plotbinsize`. + +As with pretty-print, pipe through `sed 's/\x1b\[[0-9;]*m//g'` to strip the ANSI colours. + ## Early termination Send `SIGUSR1` to the master process to trigger a clean shutdown: remaining iterations are skipped, results collected so far are printed, and all ranks call `MPI_Finalize`. diff --git a/src/common.h b/src/common.h index cf8e8a158..6021ef499 100644 --- a/src/common.h +++ b/src/common.h @@ -116,6 +116,15 @@ static int burst_pause_rand = 0; /* set by -bpdist */ static int pretty_output = 0; static int debug_mode = 0; +/* runtime-distribution plot (-plot). plot_stat selects which per-iteration + * cross-rank reduction is histogrammed; plot_bin_size (seconds, > 0) overrides + * plot_bins with a fixed bin width; plot_log selects geometric bins. */ +static int plot_output = 0; +static char plot_stat[16] = "max"; /* avg | min | max | median | mainrank */ +static int plot_bins = 10; +static double plot_bin_size = 0.0; /* 0 => use plot_bins */ +static int plot_log = 0; + /* distribution selection for burst length and pause (-bldist / -bpdist). * Values: "exp", "pareto", "lognormal". shape: α for Pareto, σ for log-normal * (ignored for exp). Defaults give exponential with shape unused. */ @@ -161,6 +170,23 @@ static inline void validate_burst_dist(const char *what, const char *dist, } } +/* Parse a duration: a number with an optional unit suffix (s, ms, us, ns). + * A bare number is interpreted as seconds (consistent with -blength/-bpause). + * Returns the value in seconds, or NAN on a malformed string / unknown unit. */ +static inline double parse_duration(const char *s) +{ + char *end = NULL; + double v = strtod(s, &end); + if (end == s) return NAN; /* no leading number */ + while (*end == ' ') end++; + if (*end == '\0') return v; /* bare number => seconds */ + if (strcmp(end, "s") == 0) return v; + if (strcmp(end, "ms") == 0) return v * 1e-3; + if (strcmp(end, "us") == 0) return v * 1e-6; + if (strcmp(end, "ns") == 0) return v * 1e-9; + return NAN; /* unknown unit */ +} + /* * Parse the standard set of command-line flags shared by every benchmark. * Unrecognised flags are compacted to the front of argv (after argv[0]) and @@ -195,6 +221,19 @@ static inline int parse_common_args(int argc, char **argv) else if (strcmp(argv[i], "-maxsamples") == 0) { max_samples = atoi(arg_value(argc, argv, &i)); } else if (strcmp(argv[i], "-pretty-print") == 0) { pretty_output = 1; } else if (strcmp(argv[i], "-debug") == 0) { debug_mode = 1; } + else if (strcmp(argv[i], "-plot") == 0) { plot_output = 1; } + else if (strcmp(argv[i], "-plotstat") == 0) { strncpy(plot_stat, arg_value(argc, argv, &i), 15); + plot_stat[15] = '\0'; } + else if (strcmp(argv[i], "-plotbins") == 0) { plot_bins = atoi(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-plotlog") == 0) { plot_log = 1; } + else if (strcmp(argv[i], "-plotbinsize") == 0) { const char *_bs = arg_value(argc, argv, &i); + plot_bin_size = parse_duration(_bs); + if (isnan(plot_bin_size) || plot_bin_size <= 0.0) { + if (my_rank == master_rank) + fprintf(stderr, "-plotbinsize must be a positive duration " + "(e.g. 2ms, 500us, 0.001), got '%s'\n", _bs); + MPI_Abort(MPI_COMM_WORLD, -1); + } } else { argv[new_argc++] = argv[i]; } /* pass through unknown flags */ } @@ -232,6 +271,27 @@ static inline int parse_common_args(int argc, char **argv) MPI_Abort(MPI_COMM_WORLD, -1); } + /* validate -plot options */ + if (plot_output) { + if (strcmp(plot_stat, "avg") && strcmp(plot_stat, "min") && strcmp(plot_stat, "max") + && strcmp(plot_stat, "median") && strcmp(plot_stat, "mainrank")) { + if (my_rank == master_rank) + fprintf(stderr, "-plotstat must be one of avg|min|max|median|mainrank, got %s\n", plot_stat); + MPI_Abort(MPI_COMM_WORLD, -1); + } + if (plot_bin_size > 0.0 && plot_log) { + if (my_rank == master_rank) + fprintf(stderr, "-plotbinsize cannot be combined with -plotlog " + "(a constant width is meaningless on a log axis)\n"); + MPI_Abort(MPI_COMM_WORLD, -1); + } + if (plot_bin_size <= 0.0 && plot_bins < 1) { + if (my_rank == master_rank) + fprintf(stderr, "-plotbins must be >= 1, got %d\n", plot_bins); + MPI_Abort(MPI_COMM_WORLD, -1); + } + } + return new_argc; } @@ -266,6 +326,116 @@ static inline void format_duration(char *buf, size_t len, double t) snprintf(buf, len, "%9.2f s", t); } +/* Strip the leading spaces format_duration adds (it right-pads to %9.2f). */ +static inline const char *ltrim_spaces(const char *s) +{ + while (*s == ' ') s++; + return s; +} + +#ifndef BLINK_PLOT_MAX_BINS +#define BLINK_PLOT_MAX_BINS 64 +#endif +#define BLINK_PLOT_BAR_MAX 40 + +/* Render an ASCII histogram of `n` per-iteration timing samples (seconds), + * followed by a percentile footer. Binning modes: + * logscale : `bins` geometric bins between min and max (needs min > 0); + * bin_size > 0 : fixed-width linear bins, lower edge aligned to a multiple of + * bin_size, capped at BLINK_PLOT_MAX_BINS with the tail folded + * into the last bin (a note is printed when this triggers); + * otherwise : `bins` equal-width linear bins between min and max. + * `vals` is sorted in place — pass a scratch array you no longer need. */ +static inline void print_runtime_histogram(double *vals, int n, const char *statname, + int bins, double bin_size, int logscale) +{ + int i, k; + printf("\n"); + if (n <= 0) { printf(" (-plot: no measured samples to plot)\n"); return; } + + qsort(vals, n, sizeof(double), compare_doubles); + double vmin = vals[0], vmax = vals[n - 1]; + + double sum = 0.0; + for (i = 0; i < n; i++) sum += vals[i]; + + if (vmax <= vmin) { /* degenerate: all identical */ + char one[24]; + format_duration(one, sizeof(one), vmin); + printf("\033[1m\033[36m Per-iteration latency \xc2\xb7 stat=%s \xc2\xb7 n=%d\033[0m\n", + statname, n); + printf(" all samples = %s\n", ltrim_spaces(one)); + return; + } + + int use_log = (logscale && vmin > 0.0); + int nb; + int overflow = 0; + double lo0 = vmin, width = 0.0, ratio = 1.0; + + if (use_log) { + nb = bins > 0 ? bins : 10; + ratio = pow(vmax / vmin, 1.0 / nb); + } else if (bin_size > 0.0) { + lo0 = floor(vmin / bin_size) * bin_size; + long need = (long)floor((vmax - lo0) / bin_size) + 1; + if (need < 1) need = 1; + if (need > BLINK_PLOT_MAX_BINS) { nb = BLINK_PLOT_MAX_BINS; overflow = 1; } + else { nb = (int)need; } + width = bin_size; + } else { + nb = bins > 0 ? bins : 10; + width = (vmax - vmin) / nb; + } + if (nb < 1) nb = 1; + + int *counts = (int *)calloc((size_t)nb, sizeof(int)); + if (!counts) { printf(" (-plot: allocation failed)\n"); return; } + for (i = 0; i < n; i++) { + int b = use_log ? (int)floor(log(vals[i] / vmin) / log(ratio)) + : (int)floor((vals[i] - lo0) / width); + if (b < 0) b = 0; + if (b >= nb) b = nb - 1; /* fold any overflow into the last bin */ + counts[b]++; + } + int maxc = 1; + for (i = 0; i < nb; i++) if (counts[i] > maxc) maxc = counts[i]; + + printf("\033[1m\033[36m Per-iteration latency \xc2\xb7 stat=%s \xc2\xb7 n=%d \xc2\xb7 %s\033[0m\n", + statname, n, use_log ? "log bins" : (bin_size > 0.0 ? "fixed bins" : "linear bins")); + if (overflow) + printf("\033[33m note: -plotbinsize would need %ld bins for the observed range; " + "capped at %d (tail folded into the last bin)\033[0m\n", + (long)floor((vmax - lo0) / bin_size) + 1, BLINK_PLOT_MAX_BINS); + printf("\033[2m ----------------------------------------------------------------------\033[0m\n"); + + for (i = 0; i < nb; i++) { + double lo = use_log ? vmin * pow(ratio, i) : lo0 + i * width; + double hi = use_log ? vmin * pow(ratio, i + 1) : lo0 + (i + 1) * width; + char lo_s[24], hi_s[24]; + format_duration(lo_s, sizeof(lo_s), lo); + format_duration(hi_s, sizeof(hi_s), hi); + int bar = (int)floor((double)counts[i] / maxc * BLINK_PLOT_BAR_MAX + 0.5); + printf(" [%s - %s] ", lo_s, hi_s); + for (k = 0; k < bar; k++) printf("\xe2\x96\x88"); /* full block */ + for (k = bar; k < BLINK_PLOT_BAR_MAX; k++) printf(" "); + printf(" %5.1f%%\n", 100.0 * counts[i] / n); + } + printf("\033[2m ----------------------------------------------------------------------\033[0m\n"); + + char s_min[24], s_mean[24], s_p50[24], s_p90[24], s_p99[24], s_max[24]; + format_duration(s_min, sizeof(s_min), vmin); + format_duration(s_mean, sizeof(s_mean), sum / n); + format_duration(s_p50, sizeof(s_p50), vals[(int)(0.50 * (n - 1) + 0.5)]); + format_duration(s_p90, sizeof(s_p90), vals[(int)(0.90 * (n - 1) + 0.5)]); + format_duration(s_p99, sizeof(s_p99), vals[(int)(0.99 * (n - 1) + 0.5)]); + format_duration(s_max, sizeof(s_max), vmax); + printf(" n=%d \xc2\xb7 min %s \xc2\xb7 mean %s \xc2\xb7 p50 %s \xc2\xb7 p90 %s \xc2\xb7 p99 %s \xc2\xb7 max %s\n", + n, ltrim_spaces(s_min), ltrim_spaces(s_mean), ltrim_spaces(s_p50), + ltrim_spaces(s_p90), ltrim_spaces(s_p99), ltrim_spaces(s_max)); + free(counts); +} + static inline void write_results() { double duration_sum; @@ -333,6 +503,20 @@ static inline void write_results() master_rank, MPI_COMM_WORLD); if (my_rank == master_rank) { + /* -plot: capture one chosen cross-rank statistic per iteration */ + int plot_stat_id = 0; /* 0=avg 1=min 2=max 3=median 4=mainrank */ + double *plot_vals = NULL; + if (plot_output) { + if (strcmp(plot_stat, "min") == 0) plot_stat_id = 1; + else if (strcmp(plot_stat, "max") == 0) plot_stat_id = 2; + else if (strcmp(plot_stat, "median") == 0) plot_stat_id = 3; + else if (strcmp(plot_stat, "mainrank") == 0) plot_stat_id = 4; + plot_vals = (double *)malloc(sizeof(double) * (num_samples > 0 ? num_samples : 1)); + if (plot_vals == NULL) { + fprintf(stderr, "Failed to allocate plot buffer on rank %d\n", my_rank); + MPI_Abort(MPI_COMM_WORLD, -1); + } + } for (i = 0; i < num_samples; i++) { int j; duration_sum = 0; @@ -347,6 +531,16 @@ static inline void write_results() else /* odd: median is the middle value */ duration_median = sorting_buf[(w_size-1)/2]; + if (plot_output) { + switch (plot_stat_id) { + case 1: plot_vals[i] = sorting_buf[0]; break; + case 2: plot_vals[i] = sorting_buf[w_size - 1]; break; + case 3: plot_vals[i] = duration_median; break; + case 4: plot_vals[i] = tmp_buf[i]; break; + default: plot_vals[i] = duration_sum / w_size; break; + } + } + if (pretty_output) { char avg_s[24], min_s[24], max_s[24], med_s[24]; format_duration(avg_s, sizeof(avg_s), duration_sum / w_size); @@ -371,6 +565,14 @@ static inline void write_results() } else { printf("Ran %lld iterations. Measured %d iterations.\n", curr_iters, num_samples); } + + /* -plot: additive ASCII histogram of the chosen per-iteration statistic */ + if (plot_output) { + print_runtime_histogram(plot_vals, num_samples, plot_stat, + plot_bins, plot_bin_size, plot_log); + free(plot_vals); + } + fflush(stdout); } diff --git a/tests/test_misc.cpp b/tests/test_misc.cpp index f3bcd1ac4..005d13cf4 100644 --- a/tests/test_misc.cpp +++ b/tests/test_misc.cpp @@ -240,3 +240,51 @@ TEST(CliGuards, MissingFlagValueAbortsCleanly) EXPECT_NE(r.stderr_raw.find("Missing value for option -mode"), std::string::npos) << "expected a 'Missing value' diagnostic on stderr.\nstderr:\n" << r.stderr_raw; } + +/* ── -plot distribution histogram ───────────────────────────────────────────── */ + +TEST(Plot, RendersHistogramAdditively) +{ + DebugRun r = run_debug_capture("alltoall_nb", 8, "-iter 100 -plot"); + ASSERT_TRUE(r.normal_exit) << "stderr:\n" << r.stderr_raw; + ASSERT_EQ(r.exit_code, 0) << "stderr:\n" << r.stderr_raw; + EXPECT_NE(r.stdout_raw.find("Per-iteration latency"), std::string::npos) + << "no plot header.\nstdout:\n" << r.stdout_raw; + EXPECT_NE(r.stdout_raw.find("stat=max"), std::string::npos) /* default stat */ + << "default -plotstat should be max.\nstdout:\n" << r.stdout_raw; + EXPECT_NE(r.stdout_raw.find("Measured"), std::string::npos) /* additive */ + << "plot should be additive (the listing is still printed).\nstdout:\n" << r.stdout_raw; +} + +TEST(Plot, StatAndBinningSelectors) +{ + DebugRun avg = run_debug_capture("alltoall_nb", 8, "-iter 100 -plot -plotstat avg"); + EXPECT_EQ(avg.exit_code, 0) << avg.stderr_raw; + EXPECT_NE(avg.stdout_raw.find("stat=avg"), std::string::npos) << avg.stdout_raw; + + DebugRun lg = run_debug_capture("alltoall_nb", 8, "-iter 100 -plot -plotlog"); + EXPECT_EQ(lg.exit_code, 0) << lg.stderr_raw; + EXPECT_NE(lg.stdout_raw.find("log bins"), std::string::npos) << lg.stdout_raw; + + DebugRun fx = run_debug_capture("alltoall_nb", 8, "-iter 100 -plot -plotbinsize 1us"); + EXPECT_EQ(fx.exit_code, 0) << fx.stderr_raw; + EXPECT_NE(fx.stdout_raw.find("fixed bins"), std::string::npos) << fx.stdout_raw; +} + +TEST(Plot, InvalidStatAbortsCleanly) +{ + DebugRun r = run_debug_capture("alltoall_nb", 8, "-plot -plotstat bogus"); + EXPECT_FALSE(r.normal_exit && r.exit_code == 0) + << "invalid -plotstat should abort.\nstdout:\n" << r.stdout_raw; + EXPECT_NE(r.stderr_raw.find("plotstat must be one of"), std::string::npos) + << "expected a -plotstat diagnostic.\nstderr:\n" << r.stderr_raw; +} + +TEST(Plot, BinsizeWithLogConflictAborts) +{ + DebugRun r = run_debug_capture("alltoall_nb", 8, "-plot -plotbinsize 1us -plotlog"); + EXPECT_FALSE(r.normal_exit && r.exit_code == 0) + << "combining -plotbinsize with -plotlog should abort.\nstdout:\n" << r.stdout_raw; + EXPECT_NE(r.stderr_raw.find("cannot be combined with -plotlog"), std::string::npos) + << "expected a conflict diagnostic.\nstderr:\n" << r.stderr_raw; +} From 79c52ce28b2a2053f6879064c567c780189e94e1 Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Sat, 6 Jun 2026 21:31:48 +0200 Subject: [PATCH 15/26] Refactor: share one histogram-row renderer between -plot and dist_test Extract print_histogram_row() into common.h (range label + scaled bar + empirical %, with an optional theoretical-% overlay and inf-edge handling). print_runtime_histogram and dist_test's print_histogram now both delegate row rendering to it; dist_test keeps its mean-relative bins and CDF overlay (its purpose) but no longer duplicates the bar-drawing/formatting, and the dead HIST_BAR_MAX is removed. Co-Authored-By: Claude Opus 4.8 --- src/common.h | 32 +++++++++++++++++++++++--------- src/tools/dist_test.c | 26 ++++++-------------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/common.h b/src/common.h index 6021ef499..18a98038c 100644 --- a/src/common.h +++ b/src/common.h @@ -338,6 +338,27 @@ static inline const char *ltrim_spaces(const char *s) #endif #define BLINK_PLOT_BAR_MAX 40 +/* Print one histogram row: an aligned [lo - hi] range label, a bar scaled to + * the tallest bin (`maxc`), the empirical percentage, and — when `theory` >= 0 + * — a theoretical percentage alongside it (dist_test's CDF overlay). An + * infinite `hi` is rendered as "inf" for an open-ended overflow bin. Shared by + * the -plot histogram and dist_test's sampler-verification histogram. */ +static inline void print_histogram_row(double lo, double hi, int count, int maxc, + int n, double theory) +{ + char lo_s[24], hi_s[24]; + int k, bar; + format_duration(lo_s, sizeof(lo_s), lo); + if (isinf(hi)) snprintf(hi_s, sizeof(hi_s), " %8s", "inf"); + else format_duration(hi_s, sizeof(hi_s), hi); + bar = (maxc > 0) ? (int)floor((double)count / maxc * BLINK_PLOT_BAR_MAX + 0.5) : 0; + printf(" [%s - %s] ", lo_s, hi_s); + for (k = 0; k < bar; k++) printf("\xe2\x96\x88"); /* full block */ + for (k = bar; k < BLINK_PLOT_BAR_MAX; k++) printf(" "); + if (theory >= 0.0) printf(" %5.1f%% (theory %5.1f%%)\n", 100.0 * count / n, 100.0 * theory); + else printf(" %5.1f%%\n", 100.0 * count / n); +} + /* Render an ASCII histogram of `n` per-iteration timing samples (seconds), * followed by a percentile footer. Binning modes: * logscale : `bins` geometric bins between min and max (needs min > 0); @@ -349,7 +370,7 @@ static inline const char *ltrim_spaces(const char *s) static inline void print_runtime_histogram(double *vals, int n, const char *statname, int bins, double bin_size, int logscale) { - int i, k; + int i; printf("\n"); if (n <= 0) { printf(" (-plot: no measured samples to plot)\n"); return; } @@ -412,14 +433,7 @@ static inline void print_runtime_histogram(double *vals, int n, const char *stat for (i = 0; i < nb; i++) { double lo = use_log ? vmin * pow(ratio, i) : lo0 + i * width; double hi = use_log ? vmin * pow(ratio, i + 1) : lo0 + (i + 1) * width; - char lo_s[24], hi_s[24]; - format_duration(lo_s, sizeof(lo_s), lo); - format_duration(hi_s, sizeof(hi_s), hi); - int bar = (int)floor((double)counts[i] / maxc * BLINK_PLOT_BAR_MAX + 0.5); - printf(" [%s - %s] ", lo_s, hi_s); - for (k = 0; k < bar; k++) printf("\xe2\x96\x88"); /* full block */ - for (k = bar; k < BLINK_PLOT_BAR_MAX; k++) printf(" "); - printf(" %5.1f%%\n", 100.0 * counts[i] / n); + print_histogram_row(lo, hi, counts[i], maxc, n, -1.0); /* -1 => no theory overlay */ } printf("\033[2m ----------------------------------------------------------------------\033[0m\n"); diff --git a/src/tools/dist_test.c b/src/tools/dist_test.c index b741b8e8d..8336dd6aa 100644 --- a/src/tools/dist_test.c +++ b/src/tools/dist_test.c @@ -21,7 +21,6 @@ #define N_SAMPLES 100000 #define HIST_NBINS 8 -#define HIST_BAR_MAX 36 /* Bin edges expressed as multiples of mean. The last bin is open-ended (overflow). */ static const double EDGES[] = { 0.0, 0.5, 1.0, 1.5, 2.0, 3.0, 5.0, 10.0 }; @@ -96,25 +95,12 @@ static void print_histogram(double *sorted, int n, printf("\n"); for (b = 0; b < HIST_NBINS; b++) { - double lo = EDGES[b] * mean; - double hi = (b < HIST_NBINS - 1) ? EDGES[b + 1] * mean : INFINITY; - double theory = cdf(isinf(hi) ? 1e300 : hi, p1, p2) - cdf(lo, p1, p2); - double emp = (double)counts[b] / n; - int bar_len = (int)round(emp / ((double)max_count / n) * HIST_BAR_MAX); - - /* format the two bin-edge strings (12 chars each, from format_duration) */ - char lo_s[24], hi_s[24]; - format_duration(lo_s, sizeof(lo_s), lo); - if (isinf(hi)) - snprintf(hi_s, sizeof(hi_s), " %8s", "inf"); - else - format_duration(hi_s, sizeof(hi_s), hi); - - /* print range, bar, empirical %, theoretical % */ - printf(" [%s - %s] ", lo_s, hi_s); - for (i = 0; i < bar_len; i++) printf("█"); - for (i = bar_len; i < HIST_BAR_MAX; i++) printf(" "); - printf(" %5.1f%% (theory %5.1f%%)\n", emp * 100.0, theory * 100.0); + double lo = EDGES[b] * mean; + double hi = (b < HIST_NBINS - 1) ? EDGES[b + 1] * mean : INFINITY; + double theory = cdf(isinf(hi) ? 1e300 : hi, p1, p2) - cdf(lo, p1, p2); + /* shared renderer (common.h): range label, scaled bar, empirical %, + * and the theoretical % overlay (theory >= 0) */ + print_histogram_row(lo, hi, counts[b], max_count, n, theory); } printf("\n"); } From c606d8c9555a5a749f50890471f817da02925c41 Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Sat, 6 Jun 2026 21:42:13 +0200 Subject: [PATCH 16/26] Add MIT LICENSE and CodeQL static-analysis workflow, with README badges LICENSE: MIT (2024-2026). codeql.yml: GitHub-native C/C++ static analysis on pushes/PRs to main/dev and weekly; manual CMake+OpenMPI build with tests off so it scans the benchmark sources. README: add License and CodeQL badges (CodeQL pinned to dev). Co-Authored-By: Claude Opus 4.8 --- .github/workflows/codeql.yml | 46 ++++++++++++++++++++++++++++++++++++ LICENSE | 21 ++++++++++++++++ README.md | 2 ++ 3 files changed, 69 insertions(+) create mode 100644 .github/workflows/codeql.yml create mode 100644 LICENSE diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..2df8eaaf2 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,46 @@ +name: CodeQL + +# Static analysis for C/C++. Runs on pushes/PRs to the main branches and +# weekly (to catch newly-published queries against unchanged code). +on: + push: + branches: [ main, dev ] + pull_request: + branches: [ main, dev ] + schedule: + - cron: '0 6 * * 1' # Mondays 06:00 UTC + +jobs: + analyze: + name: Analyze (c-cpp) + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + security-events: write # upload results to the Security tab + actions: read + contents: read + steps: + - uses: actions/checkout@v4 + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + cmake build-essential openmpi-bin libopenmpi-dev + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: c-cpp + + # Manual build (more reliable than autobuild for a CMake+MPI project). + # Tests are off so CodeQL focuses on the benchmark sources, not GTest. + - name: Build + run: | + cmake -B build -DBLINK_TESTS=OFF + cmake --build build -j"$(nproc)" + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:c-cpp" diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..0574700cd --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024-2026 Daniele De Sensi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 60930ba60..55fa3dfa5 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@
[![CI](https://github.com/hlc-lab/blink/actions/workflows/ci.yml/badge.svg?branch=dev)](https://github.com/hlc-lab/blink/actions/workflows/ci.yml?query=branch%3Adev) +[![CodeQL](https://github.com/hlc-lab/blink/actions/workflows/codeql.yml/badge.svg?branch=dev)](https://github.com/hlc-lab/blink/actions/workflows/codeql.yml?query=branch%3Adev) [![codecov](https://codecov.io/gh/hlc-lab/blink/branch/dev/graph/badge.svg)](https://codecov.io/gh/hlc-lab/blink/tree/dev) +[![License: MIT](https://img.shields.io/github/license/hlc-lab/blink)](LICENSE)
From e76e505fa586f2b3ecc53b7422c109cf9e49cc7e Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Sat, 6 Jun 2026 21:43:43 +0200 Subject: [PATCH 17/26] [FIX] Minor --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 0574700cd..defa5e082 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024-2026 Daniele De Sensi +Copyright (c) 2024-2026 HLC-Lab Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 4abe76d1bb75efc90662152ace7903df7bbb145e Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Mon, 8 Jun 2026 20:17:42 +0200 Subject: [PATCH 18/26] Remove unreachable post-malloc_align NULL checks (dead code) malloc_align() aborts internally on failure and never returns NULL, so the subsequent if(buf==NULL){fprintf;MPI_Abort;} blocks in the benchmarks were dead code. Removed 35 such blocks; common.h-s genuine plain-malloc check (all_data/sorting_buf/plot_vals) is unchanged. Co-Authored-By: Claude Opus 4.8 --- .gitignore | 8 ++++++++ src/allgather/allgather_b.c | 4 ---- src/allgather/allgather_comm_only.cpp | 4 ---- src/allgather/allgather_nb.c | 4 ---- src/allreduce/allreduce_b.c | 4 ---- src/allreduce/allreduce_nb.c | 4 ---- src/alltoall/alltoall_b.c | 4 ---- src/alltoall/alltoall_man.c | 4 ---- src/alltoall/alltoall_nb.c | 4 ---- src/barrier/barrier_b.c | 4 ---- src/barrier/barrier_nb.c | 4 ---- src/broadcast/broadcast_b.c | 4 ---- src/broadcast/broadcast_nb.c | 4 ---- src/gather/gather_b.c | 4 ---- src/gather/gather_nb.c | 4 ---- src/incast/incast_b.c | 4 ---- src/incast/incast_bsnbr.c | 4 ---- src/incast/incast_get.c | 4 ---- src/incast/incast_nb.c | 4 ---- src/incast/incast_put.c | 4 ---- src/kpartners/kpartners_nb.c | 4 ---- src/pairwise/pairwise_b.c | 4 ---- src/pairwise/pairwise_bsnbr.c | 4 ---- src/pairwise/pairwise_nb.c | 4 ---- src/pingpong/pingpong_b.c | 4 ---- src/pingpong/pingpong_pairwise_b.c | 4 ---- src/reduce/reduce_b.c | 4 ---- src/reduce/reduce_nb.c | 4 ---- src/reduce_scatter/reduce_scatter_b.c | 4 ---- src/reduce_scatter/reduce_scatter_nb.c | 4 ---- src/ring/ring_bsnbr.c | 4 ---- src/ring/ring_nb.c | 4 ---- src/scatter/scatter_b.c | 4 ---- src/scatter/scatter_nb.c | 4 ---- src/stencil/stencil_2d_nb.c | 4 ---- src/tools/checker.c | 4 ---- 36 files changed, 8 insertions(+), 140 deletions(-) diff --git a/.gitignore b/.gitignore index 567609b12..6d0c0669c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,9 @@ build/ +build*/ + +# coverage artifacts (generated; uploaded to Codecov by CI, never committed) +coverage.xml +coverage*.html +*.gcda +*.gcno +*.gcov diff --git a/src/allgather/allgather_b.c b/src/allgather/allgather_b.c index 158b857bc..6f728bbcc 100644 --- a/src/allgather/allgather_b.c +++ b/src/allgather/allgather_b.c @@ -48,10 +48,6 @@ int main(int argc, char** argv){ recv_buf=(unsigned char*)malloc_align(recv_buf_size); durations=(double *)malloc_align(sizeof(double)*max_samples); - if(send_buf==NULL || recv_buf==NULL || durations==NULL){ - fprintf(stderr,"Failed to allocate a buffer on rank %d\n",my_rank); - MPI_Abort(MPI_COMM_WORLD, -1); - } /*fill send buffer with dummies*/ for(i=0;i Date: Mon, 8 Jun 2026 20:17:42 +0200 Subject: [PATCH 19/26] coverage: exercise burst sampling, checker, null_dummy; tune gcovr Tests: drive -bldist/-bpdist/-bpause (covers the samplers, rand_duration, dsleep and sample_* from the benchmark side, which merges correctly) plus checker/null_dummy smoke tests. gcovr: exclude unreachable/throw branches and MPI_Abort error-exit lines (uncoverable - the process dies before .gcda is flushed). .gitignore: ignore coverage artifacts. Raises src/ coverage to ~83% lines / 86% functions / 60% branches; 8/8 suites pass. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 2 ++ README.md | 3 ++- tests/test_misc.cpp | 47 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c70c2cd3a..e70d4224a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,8 @@ jobs: continue-on-error: true run: | gcovr --root . --filter 'src/' \ + --exclude-unreachable-branches --exclude-throw-branches \ + --exclude-lines-by-pattern '.*MPI_Abort.*' \ --xml-pretty -o coverage.xml \ --print-summary | tee coverage_summary.txt { diff --git a/README.md b/README.md index 55fa3dfa5..a9d49aef7 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,8 @@ uploaded to Codecov. To reproduce a coverage run locally: cmake -B build-cov -DBLINK_TESTS=ON -DBLINK_COVERAGE=ON cmake --build build-cov -j$(nproc) ctest --test-dir build-cov --output-on-failure -gcovr --root . --filter 'src/' --print-summary +gcovr --root . --filter 'src/' --exclude-unreachable-branches --exclude-throw-branches \ + --exclude-lines-by-pattern '.*MPI_Abort.*' --print-summary ``` ## Common flags diff --git a/tests/test_misc.cpp b/tests/test_misc.cpp index 005d13cf4..3b383c182 100644 --- a/tests/test_misc.cpp +++ b/tests/test_misc.cpp @@ -288,3 +288,50 @@ TEST(Plot, BinsizeWithLogConflictAborts) EXPECT_NE(r.stderr_raw.find("cannot be combined with -plotlog"), std::string::npos) << "expected a conflict diagnostic.\nstderr:\n" << r.stderr_raw; } + +/* ── burst / pause distribution sampling ───────────────────────────────────── + * Exercises the randomised inter-arrival path from the benchmark side: + * sample_burst_length/sample_pause_length -> rand_duration -> rand_{exp,pareto, + * lognormal}, plus dsleep(). barrier_nb is the cheapest benchmark to drive it. */ +TEST(BurstSampling, RandomisedBurstAndPause) +{ + for (const char *d : {"exp", "pareto", "lognormal"}) { + std::string extra = std::string("-iter 5 -blength 0.0004 -bldist ") + d; + if (std::string(d) != "exp") extra += " -blshape 1.6"; /* shape ignored for exp */ + DebugRun r = run_debug_capture("barrier_nb", 8, extra); + EXPECT_TRUE(r.normal_exit && r.exit_code == 0) + << "barrier_nb -bldist " << d << " failed.\nstderr:\n" << r.stderr_raw; + } + /* randomised pause drives dsleep() + sample_pause_length() */ + DebugRun p = run_debug_capture("barrier_nb", 8, "-iter 4 -bpause 0.0004 -bpdist exp"); + EXPECT_TRUE(p.normal_exit && p.exit_code == 0) + << "barrier_nb -bpdist failed.\nstderr:\n" << p.stderr_raw; +} + +TEST(BurstSampling, InvalidShapeAbortsCleanly) +{ + DebugRun r = run_debug_capture("barrier_nb", 8, "-blength 0.001 -bldist pareto -blshape 0.5"); + EXPECT_FALSE(r.normal_exit && r.exit_code == 0) + << "a Pareto shape <= 1 should abort.\nstdout:\n" << r.stdout_raw; + EXPECT_NE(r.stderr_raw.find("pareto shape"), std::string::npos) + << "expected a pareto-shape diagnostic.\nstderr:\n" << r.stderr_raw; +} + +/* ── auxiliary tools (checker, null_dummy) ──────────────────────────────────── */ + +TEST(Tools, CheckerRuns) +{ + /* checker = all-to-all plus a per-iteration timestamp log; just exercise it. */ + DebugRun r = run_debug_capture("checker", 8, "-iter 10"); + EXPECT_TRUE(r.normal_exit) << "stderr:\n" << r.stderr_raw; + EXPECT_EQ(r.exit_code, 0) << "stderr:\n" << r.stderr_raw; + EXPECT_NE(r.stdout_raw.find("Measured"), std::string::npos) + << "checker produced no results footer.\nstdout:\n" << r.stdout_raw; +} + +TEST(Tools, NullDummyRuns) +{ + DebugRun r = run_debug_capture("null_dummy", 1, ""); + EXPECT_TRUE(r.normal_exit) << "stderr:\n" << r.stderr_raw; + EXPECT_EQ(r.exit_code, 0) << "stderr:\n" << r.stderr_raw; +} From 2e49ab7081482f8a917206467e46ff8805bf49ec Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Mon, 8 Jun 2026 21:07:15 +0200 Subject: [PATCH 20/26] Refactor: compile shared runtime once (common.h -> common.h + common.c) Move the definitions out of common.h (a header of static-inline functions instantiated in ~40 TUs) into common.c, compiled once into a blink_common static library and linked into every benchmark. common.h is now declarations only, wrapped in extern "C" so the C++ comm_only benchmarks link to the C library. Cleaner, faster to build, and -- the motivation -- gcov now counts the shared code from a single .gcda instead of per-TU copies it cannot merge: coverage of src/ rises to ~88% lines / 100% functions / 67% branches with no metric gaming. Co-Authored-By: Claude Opus 4.8 --- src/CMakeLists.txt | 12 + src/common.c | 738 +++++++++++++++++++++++++++++++++++++++++ src/common.h | 804 +++++---------------------------------------- 3 files changed, 827 insertions(+), 727 deletions(-) create mode 100644 src/common.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 00bc8baa0..da1964139 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,8 +4,19 @@ # All executable names are derived from the source filename stem, e.g. # alltoall/alltoall_nb.c -> bin/alltoall_nb +# The shared runtime (common.c) is compiled once into a static library and +# linked into every benchmark — cleaner and faster than re-instantiating a +# header of static-inline functions in each TU, and it yields a single .gcda so +# gcov coverage of the shared code is counted accurately. MPI/m propagate to +# consumers via PUBLIC. C++ benchmarks link it too (common.h uses extern "C"). +add_library(blink_common STATIC common.c) +target_include_directories(blink_common PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(blink_common PUBLIC MPI::MPI_C m) + file(GLOB_RECURSE c_srcs CONFIGURE_DEPENDS "*.c") file(GLOB_RECURSE cpp_srcs CONFIGURE_DEPENDS "*.cpp") +# common.c is the library, not a benchmark — don't build an executable from it. +list(REMOVE_ITEM c_srcs "${CMAKE_CURRENT_SOURCE_DIR}/common.c") foreach(src IN LISTS c_srcs cpp_srcs) get_filename_component(name ${src} NAME_WE) @@ -13,6 +24,7 @@ foreach(src IN LISTS c_srcs cpp_srcs) add_executable(${name} ${src}) target_include_directories(${name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(${name} PRIVATE blink_common) if(ext STREQUAL ".c") target_link_libraries(${name} PRIVATE MPI::MPI_C m) diff --git a/src/common.c b/src/common.c new file mode 100644 index 000000000..c3c8d952b --- /dev/null +++ b/src/common.c @@ -0,0 +1,738 @@ +/* + * common.c — definitions of the shared Blink runtime (declared in common.h). + * + * Compiled once into the `blink_common` library and linked into every + * benchmark, rather than being a header full of `static inline` functions + * instantiated in each of the ~40 translation units. Besides being cleaner and + * faster to build, this gives accurate gcov coverage: a single .gcda for the + * shared code instead of per-TU copies that gcov cannot merge. + */ +#include "common.h" + +/* ── globals ─────────────────────────────────────────────────────────────── */ +int my_rank; +int w_size; +int master_rank = 0; +long long curr_iters; /* total outer iterations executed (warmup + measured) */ +long long measured_iters; /* number of recorded samples (excludes warmup) */ +int warm_up_iters = 5; +int max_samples = 1000; +double *durations; + +/* Async-signal-safe shutdown flag set by sig_handler; checked in measurement + * loops. The signal handler does no MPI / no malloc / no stdio — only sets + * this flag. The main thread observes it between iterations and exits the + * loop cleanly, then calls write_results() + MPI_Finalize() itself. */ +volatile sig_atomic_t shutdown_requested = 0; + +/* common benchmark parameters – initialised to defaults, set by parse_common_args() */ +int msg_size = 1024; +int measure_granularity = 1; +int rand_seed = 1; +int max_iters = 1; +int endless = 0; +double burst_length = 0.0; +int burst_length_rand = 0; /* set by -bldist */ +double burst_pause = 0.0; +int burst_pause_rand = 0; /* set by -bpdist */ +int pretty_output = 0; +int debug_mode = 0; + +/* runtime-distribution plot (-plot). */ +int plot_output = 0; +char plot_stat[16] = "max"; /* avg | min | max | median | mainrank */ +int plot_bins = 10; +double plot_bin_size = 0.0; /* 0 => use plot_bins */ +int plot_log = 0; + +/* distribution selection for burst length and pause (-bldist / -bpdist). */ +char burst_dist[16] = "exp"; +double burst_shape = 1.5; +char pause_dist[16] = "exp"; +double pause_shape = 1.5; + +#ifndef BLINK_PLOT_MAX_BINS +#define BLINK_PLOT_MAX_BINS 64 +#endif +#define BLINK_PLOT_BAR_MAX 40 +#define ALIGNMENT (sysconf(_SC_PAGESIZE)) + +/* ── random duration samplers ────────────────────────────────────────────── */ + +/* Exponential: shape parameter accepted for API uniformity but not used — + * the distribution is fully determined by its mean. */ +double rand_exp(double mean, double shape) +{ + (void)shape; + double u = (rand() + 0.5) / (RAND_MAX + 1.0); /* shift away from 0 and 1 */ + return -mean * log(1.0 - u); +} + +/* Pareto: shape = tail exponent α. Must be > 1 for a finite mean. + * Parameterised so that E[X] = mean regardless of α. + * Inverse-CDF method: x = x_m * u^(-1/α), u ~ Uniform(0,1). */ +double rand_pareto(double mean, double shape) +{ + double alpha = shape; + if (alpha <= 1.0) { + fprintf(stderr, "rand_pareto: shape (alpha) must be > 1, got %g\n", alpha); + exit(-1); + } + if (mean <= 0.0) { + fprintf(stderr, "rand_pareto: mean must be > 0, got %g\n", mean); + exit(-1); + } + double x_m = mean * (alpha - 1.0) / alpha; + double u = (rand() + 0.5) / (RAND_MAX + 1.0); /* shift away from 0 */ + return x_m * pow(u, -1.0 / alpha); +} + +/* Log-normal: shape = σ of the underlying normal. + * Parameterised so that E[X] = mean regardless of σ. + * Box-Muller transform. */ +double rand_lognormal(double mean, double sigma) +{ + if (mean <= 0.0) { + fprintf(stderr, "rand_lognormal: mean must be > 0, got %g\n", mean); + exit(-1); + } + if (sigma <= 0.0) { + fprintf(stderr, "rand_lognormal: sigma must be > 0, got %g\n", sigma); + exit(-1); + } + double mu = log(mean) - 0.5 * sigma * sigma; + double u1 = (rand() + 0.5) / (RAND_MAX + 1.0); /* shift away from 0 */ + double u2 = (rand() + 0.5) / (RAND_MAX + 1.0); + double z = sqrt(-2.0 * log(u1)) * cos(2.0 * M_PI * u2); + return exp(mu + sigma * z); +} + +/* Dispatch to the selected sampler. */ +double rand_duration(double mean, const char *dist, double shape) +{ + if (strcmp(dist, "pareto") == 0) return rand_pareto(mean, shape); + if (strcmp(dist, "lognormal") == 0) return rand_lognormal(mean, shape); + return rand_exp(mean, shape); +} + +/*sleep seconds given as double*/ +int dsleep(double t) +{ + struct timespec t1, t2; + t1.tv_sec = (long)t; + t1.tv_nsec = (t - t1.tv_sec) * 1000000000L; + return nanosleep(&t1, &t2); +} + +/*double comparison function for quicksort*/ +int compare_doubles(const void *p1, const void *p2) +{ + if (*(double *)p1 < *(double *)p2) + return -1; + else if (*(double *)p1 > *(double *)p2) + return 1; + else + return 0; +} + +/* Bounds-checked accessor for a flag's value argument. Aborts with a clear + * message instead of dereferencing argv[argc] (== NULL) when a value-taking + * flag is supplied as the final command-line token. */ +const char *arg_value(int argc, char **argv, int *i) +{ + if (*i + 1 >= argc) { + if (my_rank == master_rank) + fprintf(stderr, "Missing value for option %s\n", argv[*i]); + MPI_Abort(MPI_COMM_WORLD, -1); + } + return argv[++(*i)]; +} + +/* Validate a distribution name / shape pair selected via -bldist/-bpdist so a + * misconfiguration fails loudly at startup rather than silently producing + * garbage durations later (e.g. log-normal with a non-positive mean). */ +void validate_burst_dist(const char *what, const char *dist, + double mean, double shape) +{ + if (mean <= 0.0) { + if (my_rank == master_rank) + fprintf(stderr, "%s: randomised distribution requires a positive mean, got %g\n", + what, mean); + MPI_Abort(MPI_COMM_WORLD, -1); + } + if (strcmp(dist, "pareto") == 0 && shape <= 1.0) { + if (my_rank == master_rank) + fprintf(stderr, "%s: pareto shape (alpha) must be > 1, got %g\n", what, shape); + MPI_Abort(MPI_COMM_WORLD, -1); + } + if (strcmp(dist, "lognormal") == 0 && shape <= 0.0) { + if (my_rank == master_rank) + fprintf(stderr, "%s: lognormal shape (sigma) must be > 0, got %g\n", what, shape); + MPI_Abort(MPI_COMM_WORLD, -1); + } +} + +/* Parse a duration: a number with an optional unit suffix (s, ms, us, ns). + * A bare number is interpreted as seconds (consistent with -blength/-bpause). + * Returns the value in seconds, or NAN on a malformed string / unknown unit. */ +double parse_duration(const char *s) +{ + char *end = NULL; + double v = strtod(s, &end); + if (end == s) return NAN; /* no leading number */ + while (*end == ' ') end++; + if (*end == '\0') return v; /* bare number => seconds */ + if (strcmp(end, "s") == 0) return v; + if (strcmp(end, "ms") == 0) return v * 1e-3; + if (strcmp(end, "us") == 0) return v * 1e-6; + if (strcmp(end, "ns") == 0) return v * 1e-9; + return NAN; /* unknown unit */ +} + +/* + * Parse the standard set of command-line flags shared by every benchmark. + * Unrecognised flags are compacted to the front of argv (after argv[0]) and + * the new argc is returned, so each benchmark can do a second pass for its + * own flags. Also seeds the RNG and resolves -mrand after parsing. + */ +int parse_common_args(int argc, char **argv) +{ + int new_argc = 1; /* always keep argv[0] (program name) */ + int do_rand_master = 0; + int i; + + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-mrank") == 0) { master_rank = atoi(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-mrand") == 0) { do_rand_master = 1; } + else if (strcmp(argv[i], "-msgsize") == 0) { msg_size = atoi(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-endl") == 0) { endless = 1; } + else if (strcmp(argv[i], "-iter") == 0) { max_iters = atoi(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-warmup") == 0) { warm_up_iters = atoi(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-blength") == 0) { burst_length = atof(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-bpause") == 0) { burst_pause = atof(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-bldist") == 0) { strncpy(burst_dist, arg_value(argc, argv, &i), 15); + burst_dist[15] = '\0'; + burst_length_rand = 1; } + else if (strcmp(argv[i], "-bpdist") == 0) { strncpy(pause_dist, arg_value(argc, argv, &i), 15); + pause_dist[15] = '\0'; + burst_pause_rand = 1; } + else if (strcmp(argv[i], "-blshape") == 0) { burst_shape = atof(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-bpshape") == 0) { pause_shape = atof(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-seed") == 0) { rand_seed = atoi(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-grty") == 0) { measure_granularity = atoi(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-maxsamples") == 0) { max_samples = atoi(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-pretty-print") == 0) { pretty_output = 1; } + else if (strcmp(argv[i], "-debug") == 0) { debug_mode = 1; } + else if (strcmp(argv[i], "-plot") == 0) { plot_output = 1; } + else if (strcmp(argv[i], "-plotstat") == 0) { strncpy(plot_stat, arg_value(argc, argv, &i), 15); + plot_stat[15] = '\0'; } + else if (strcmp(argv[i], "-plotbins") == 0) { plot_bins = atoi(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-plotlog") == 0) { plot_log = 1; } + else if (strcmp(argv[i], "-plotbinsize") == 0) { const char *_bs = arg_value(argc, argv, &i); + plot_bin_size = parse_duration(_bs); + if (isnan(plot_bin_size) || plot_bin_size <= 0.0) { + if (my_rank == master_rank) + fprintf(stderr, "-plotbinsize must be a positive duration " + "(e.g. 2ms, 500us, 0.001), got '%s'\n", _bs); + MPI_Abort(MPI_COMM_WORLD, -1); + } } + else { argv[new_argc++] = argv[i]; } /* pass through unknown flags */ + } + + /* fail fast on nonsensical numeric parameters */ + if (max_samples < 1) { + if (my_rank == master_rank) + fprintf(stderr, "-maxsamples must be >= 1, got %d\n", max_samples); + MPI_Abort(MPI_COMM_WORLD, -1); + } + if (measure_granularity < 1) { + if (my_rank == master_rank) + fprintf(stderr, "-grty must be >= 1, got %d\n", measure_granularity); + MPI_Abort(MPI_COMM_WORLD, -1); + } + + /* a randomised burst/pause distribution needs a positive mean and a valid + * shape — validate now so we never feed log(0)/negative values to a sampler */ + if (burst_length_rand) + validate_burst_dist("-bldist", burst_dist, burst_length, burst_shape); + if (burst_pause_rand) + validate_burst_dist("-bpdist", pause_dist, burst_pause, pause_shape); + + /* seed RNG (shared across all ranks) and resolve randomised master */ + srand(rand_seed); + if (do_rand_master) + master_rank = rand() % w_size; + + /* validate the resolved master rank is a real rank. An out-of-range -mrank + * would otherwise become an invalid root in MPI_Gather/Bcast/Reduce, and can + * deadlock benchmarks that branch on (my_rank == master_rank). Gate the + * message on rank 0 because master_rank itself may be the bad value. */ + if (master_rank < 0 || master_rank >= w_size) { + if (my_rank == 0) + fprintf(stderr, "-mrank must be in [0, %d), got %d\n", w_size, master_rank); + MPI_Abort(MPI_COMM_WORLD, -1); + } + + /* validate -plot options */ + if (plot_output) { + if (strcmp(plot_stat, "avg") && strcmp(plot_stat, "min") && strcmp(plot_stat, "max") + && strcmp(plot_stat, "median") && strcmp(plot_stat, "mainrank")) { + if (my_rank == master_rank) + fprintf(stderr, "-plotstat must be one of avg|min|max|median|mainrank, got %s\n", plot_stat); + MPI_Abort(MPI_COMM_WORLD, -1); + } + if (plot_bin_size > 0.0 && plot_log) { + if (my_rank == master_rank) + fprintf(stderr, "-plotbinsize cannot be combined with -plotlog " + "(a constant width is meaningless on a log axis)\n"); + MPI_Abort(MPI_COMM_WORLD, -1); + } + if (plot_bin_size <= 0.0 && plot_bins < 1) { + if (my_rank == master_rank) + fprintf(stderr, "-plotbins must be >= 1, got %d\n", plot_bins); + MPI_Abort(MPI_COMM_WORLD, -1); + } + } + + return new_argc; +} + +/* Convenience wrappers used by benchmark measurement loops. */ +double sample_burst_length(double mean) { return rand_duration(mean, burst_dist, burst_shape); } +double sample_pause_length(double mean) { return rand_duration(mean, pause_dist, pause_shape); } + +/* Record a single measured-iteration latency into the ring buffer. + * Skips writes during warm-up: callers gate this by `if (k >= warm_up_iters)` + * at the outer iteration level — see the standard benchmark loop. + * + * The ring buffer of size `max_samples` therefore only ever holds measured + * samples (no warmup pollution); the (oldest → newest) ordering is + * (measured_iters % max_samples) ... (measured_iters - 1) % max_samples. */ +void record_duration(double t) +{ + durations[measured_iters % max_samples] = t; + measured_iters++; +} + +/* ── output helpers ──────────────────────────────────────────────────────── */ + +/*format a duration (seconds) with auto-scaled units; "%9.2f UU" is normally + * 12 chars wide (9 is a minimum field width, so extreme outliers can exceed it)*/ +void format_duration(char *buf, size_t len, double t) +{ + if (t < 1e-3) + snprintf(buf, len, "%9.2f us", t * 1e6); + else if (t < 1.0) + snprintf(buf, len, "%9.2f ms", t * 1e3); + else + snprintf(buf, len, "%9.2f s", t); +} + +/* Strip the leading spaces format_duration adds (it right-pads to %9.2f). */ +const char *ltrim_spaces(const char *s) +{ + while (*s == ' ') s++; + return s; +} + +/* Print one histogram row: an aligned [lo - hi] range label, a bar scaled to + * the tallest bin (`maxc`), the empirical percentage, and — when `theory` >= 0 + * — a theoretical percentage alongside it (dist_test's CDF overlay). An + * infinite `hi` is rendered as "inf" for an open-ended overflow bin. Shared by + * the -plot histogram and dist_test's sampler-verification histogram. */ +void print_histogram_row(double lo, double hi, int count, int maxc, + int n, double theory) +{ + char lo_s[24], hi_s[24]; + int k, bar; + format_duration(lo_s, sizeof(lo_s), lo); + if (isinf(hi)) snprintf(hi_s, sizeof(hi_s), " %8s", "inf"); + else format_duration(hi_s, sizeof(hi_s), hi); + bar = (maxc > 0) ? (int)floor((double)count / maxc * BLINK_PLOT_BAR_MAX + 0.5) : 0; + printf(" [%s - %s] ", lo_s, hi_s); + for (k = 0; k < bar; k++) printf("\xe2\x96\x88"); /* full block */ + for (k = bar; k < BLINK_PLOT_BAR_MAX; k++) printf(" "); + if (theory >= 0.0) printf(" %5.1f%% (theory %5.1f%%)\n", 100.0 * count / n, 100.0 * theory); + else printf(" %5.1f%%\n", 100.0 * count / n); +} + +/* Render an ASCII histogram of `n` per-iteration timing samples (seconds), + * followed by a percentile footer. Binning modes: + * logscale : `bins` geometric bins between min and max (needs min > 0); + * bin_size > 0 : fixed-width linear bins, lower edge aligned to a multiple of + * bin_size, capped at BLINK_PLOT_MAX_BINS with the tail folded + * into the last bin (a note is printed when this triggers); + * otherwise : `bins` equal-width linear bins between min and max. + * `vals` is sorted in place — pass a scratch array you no longer need. */ +void print_runtime_histogram(double *vals, int n, const char *statname, + int bins, double bin_size, int logscale) +{ + int i; + printf("\n"); + if (n <= 0) { printf(" (-plot: no measured samples to plot)\n"); return; } + + qsort(vals, n, sizeof(double), compare_doubles); + double vmin = vals[0], vmax = vals[n - 1]; + + double sum = 0.0; + for (i = 0; i < n; i++) sum += vals[i]; + + if (vmax <= vmin) { /* degenerate: all identical */ + char one[24]; + format_duration(one, sizeof(one), vmin); + printf("\033[1m\033[36m Per-iteration latency \xc2\xb7 stat=%s \xc2\xb7 n=%d\033[0m\n", + statname, n); + printf(" all samples = %s\n", ltrim_spaces(one)); + return; + } + + int use_log = (logscale && vmin > 0.0); + int nb; + int overflow = 0; + double lo0 = vmin, width = 0.0, ratio = 1.0; + + if (use_log) { + nb = bins > 0 ? bins : 10; + ratio = pow(vmax / vmin, 1.0 / nb); + } else if (bin_size > 0.0) { + lo0 = floor(vmin / bin_size) * bin_size; + long need = (long)floor((vmax - lo0) / bin_size) + 1; + if (need < 1) need = 1; + if (need > BLINK_PLOT_MAX_BINS) { nb = BLINK_PLOT_MAX_BINS; overflow = 1; } + else { nb = (int)need; } + width = bin_size; + } else { + nb = bins > 0 ? bins : 10; + width = (vmax - vmin) / nb; + } + if (nb < 1) nb = 1; + + int *counts = (int *)calloc((size_t)nb, sizeof(int)); + if (!counts) { printf(" (-plot: allocation failed)\n"); return; } + for (i = 0; i < n; i++) { + int b = use_log ? (int)floor(log(vals[i] / vmin) / log(ratio)) + : (int)floor((vals[i] - lo0) / width); + if (b < 0) b = 0; + if (b >= nb) b = nb - 1; /* fold any overflow into the last bin */ + counts[b]++; + } + int maxc = 1; + for (i = 0; i < nb; i++) if (counts[i] > maxc) maxc = counts[i]; + + printf("\033[1m\033[36m Per-iteration latency \xc2\xb7 stat=%s \xc2\xb7 n=%d \xc2\xb7 %s\033[0m\n", + statname, n, use_log ? "log bins" : (bin_size > 0.0 ? "fixed bins" : "linear bins")); + if (overflow) + printf("\033[33m note: -plotbinsize would need %ld bins for the observed range; " + "capped at %d (tail folded into the last bin)\033[0m\n", + (long)floor((vmax - lo0) / bin_size) + 1, BLINK_PLOT_MAX_BINS); + printf("\033[2m ----------------------------------------------------------------------\033[0m\n"); + + for (i = 0; i < nb; i++) { + double lo = use_log ? vmin * pow(ratio, i) : lo0 + i * width; + double hi = use_log ? vmin * pow(ratio, i + 1) : lo0 + (i + 1) * width; + print_histogram_row(lo, hi, counts[i], maxc, n, -1.0); /* -1 => no theory overlay */ + } + printf("\033[2m ----------------------------------------------------------------------\033[0m\n"); + + char s_min[24], s_mean[24], s_p50[24], s_p90[24], s_p99[24], s_max[24]; + format_duration(s_min, sizeof(s_min), vmin); + format_duration(s_mean, sizeof(s_mean), sum / n); + format_duration(s_p50, sizeof(s_p50), vals[(int)(0.50 * (n - 1) + 0.5)]); + format_duration(s_p90, sizeof(s_p90), vals[(int)(0.90 * (n - 1) + 0.5)]); + format_duration(s_p99, sizeof(s_p99), vals[(int)(0.99 * (n - 1) + 0.5)]); + format_duration(s_max, sizeof(s_max), vmax); + printf(" n=%d \xc2\xb7 min %s \xc2\xb7 mean %s \xc2\xb7 p50 %s \xc2\xb7 p90 %s \xc2\xb7 p99 %s \xc2\xb7 max %s\n", + n, ltrim_spaces(s_min), ltrim_spaces(s_mean), ltrim_spaces(s_p50), + ltrim_spaces(s_p90), ltrim_spaces(s_p99), ltrim_spaces(s_max)); + free(counts); +} + +void write_results(void) +{ + double duration_sum; + double duration_median; + int num_samples; + int i; + int start_index; + double *tmp_buf = NULL; + + if (measured_iters > max_samples) /* wrapped the sampling ring buffer */ + { + num_samples = max_samples; + start_index = (int)(measured_iters % max_samples); + } + else + { + num_samples = (int)measured_iters; + start_index = 0; + } + + /* MPI_Gather below uses num_samples as BOTH send- and recv-count, which is + * only valid if every rank contributes the same count. Ranks normally stay + * in lockstep, but to be robust (e.g. a SIGUSR1 shutdown that reaches ranks + * at slightly different iterations) collectively agree on the common minimum + * so the gather can never mismatch. */ + { + int common_samples = num_samples; + MPI_Allreduce(&num_samples, &common_samples, 1, MPI_INT, MPI_MIN, MPI_COMM_WORLD); + /* keep only the most recent common_samples (drop the oldest extras) */ + start_index += (num_samples - common_samples); + num_samples = common_samples; + } + + tmp_buf = (double *)malloc(sizeof(double) * (num_samples > 0 ? num_samples : 1)); + /* copy in chronological order (oldest → newest) using circular indexing */ + for (i = 0; i < num_samples; i++) { + tmp_buf[i] = durations[(start_index + i) % max_samples]; + } + + double *all_data = (double *)malloc(sizeof(double)*(num_samples > 0 ? num_samples : 1)*w_size); + double *sorting_buf = (double *)malloc(sizeof(double)*w_size); + + if (all_data == NULL || sorting_buf == NULL) { + fprintf(stderr, "Failed to allocate a buffer on rank %d\n", my_rank); + MPI_Abort(MPI_COMM_WORLD, -1); + } + + /* print header before the gather so it appears first */ + if (my_rank == master_rank) { + if (pretty_output) { + printf("\033[1m\033[36m" + " %5s %12s %12s %12s %12s" + "\033[0m\n", + "#", "avg", "min", "max", "median"); + printf("\033[2m" + " -------------------------------------------------------------" + "\033[0m\n"); + } else { + printf("Average,Minimum,Maximum,Median,MainRank\n"); + } + } + + MPI_Gather(tmp_buf, num_samples, MPI_DOUBLE, + all_data, num_samples, MPI_DOUBLE, + master_rank, MPI_COMM_WORLD); + + if (my_rank == master_rank) { + /* -plot: capture one chosen cross-rank statistic per iteration */ + int plot_stat_id = 0; /* 0=avg 1=min 2=max 3=median 4=mainrank */ + double *plot_vals = NULL; + if (plot_output) { + if (strcmp(plot_stat, "min") == 0) plot_stat_id = 1; + else if (strcmp(plot_stat, "max") == 0) plot_stat_id = 2; + else if (strcmp(plot_stat, "median") == 0) plot_stat_id = 3; + else if (strcmp(plot_stat, "mainrank") == 0) plot_stat_id = 4; + plot_vals = (double *)malloc(sizeof(double) * (num_samples > 0 ? num_samples : 1)); + if (plot_vals == NULL) { + fprintf(stderr, "Failed to allocate plot buffer on rank %d\n", my_rank); + MPI_Abort(MPI_COMM_WORLD, -1); + } + } + for (i = 0; i < num_samples; i++) { + int j; + duration_sum = 0; + for (j = 0; j < w_size; j++) { + sorting_buf[j] = all_data[j*num_samples + i]; + duration_sum += sorting_buf[j]; + } + + qsort(sorting_buf, w_size, sizeof(double), compare_doubles); + if (w_size % 2 == 0) /* even: median as mean of two middle values */ + duration_median = (sorting_buf[(w_size-1)/2] + sorting_buf[w_size/2]) / 2.0; + else /* odd: median is the middle value */ + duration_median = sorting_buf[(w_size-1)/2]; + + if (plot_output) { + switch (plot_stat_id) { + case 1: plot_vals[i] = sorting_buf[0]; break; + case 2: plot_vals[i] = sorting_buf[w_size - 1]; break; + case 3: plot_vals[i] = duration_median; break; + case 4: plot_vals[i] = tmp_buf[i]; break; + default: plot_vals[i] = duration_sum / w_size; break; + } + } + + if (pretty_output) { + char avg_s[24], min_s[24], max_s[24], med_s[24]; + format_duration(avg_s, sizeof(avg_s), duration_sum / w_size); + format_duration(min_s, sizeof(min_s), sorting_buf[0]); + format_duration(max_s, sizeof(max_s), sorting_buf[w_size-1]); + format_duration(med_s, sizeof(med_s), duration_median); + printf(" %5d %12s %12s %12s %12s\n", + i+1, avg_s, min_s, max_s, med_s); + } else { + printf("%.9f,%.9f,%.9f,%.9f,%.9f\n", + duration_sum / w_size, + sorting_buf[0], sorting_buf[w_size-1], + duration_median, tmp_buf[i]); + } + } + + if (pretty_output) { + printf("\033[2m" + " -------------------------------------------------------------" + "\033[0m\n"); + printf(" %d samples \xc2\xb7 %lld iterations total\n", num_samples, curr_iters); + } else { + printf("Ran %lld iterations. Measured %d iterations.\n", curr_iters, num_samples); + } + + /* -plot: additive ASCII histogram of the chosen per-iteration statistic */ + if (plot_output) { + print_runtime_histogram(plot_vals, num_samples, plot_stat, + plot_bins, plot_bin_size, plot_log); + free(plot_vals); + } + + fflush(stdout); + } + + free(sorting_buf); + free(all_data); + free(tmp_buf); +} + +/* Async-signal-safe handler: only sets a flag. The measurement loop in each + * benchmark observes the flag and exits cleanly; write_results() and + * MPI_Finalize() happen on the main thread, never from signal context. */ +void sig_handler(int sig) +{ + (void)sig; + shutdown_requested = 1; +} + +/* Install the SIGUSR1 shutdown handler with sigaction() rather than signal(), + * for portable, persistent (non-one-shot) semantics across platforms. */ +void install_shutdown_handler(void) +{ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sig_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGUSR1, &sa, NULL); +} + +/* Collectively decide whether to shut down. SIGUSR1 only sets the flag on the + * rank that received it, so a purely local check would make ranks leave the + * measurement loop at different iterations — desynchronising the per-iteration + * collectives and the final MPI_Gather (deadlock / mismatch). This all-reduce + * (called once per outer iteration, OUTSIDE the timed region) guarantees every + * rank breaks on the same iteration. Returns non-zero if any rank requested + * shutdown. */ +int check_shutdown(void) +{ + int local = (int)shutdown_requested; + int global = 0; + MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MAX, MPI_COMM_WORLD); + return global; +} + +/* ── combinatorics helpers ───────────────────────────────────────────────── */ + +/*use Fisher-Yates to permute array*/ +void permute(int *a, int n) +{ + int j, t, i; + for (i = n; i > 1; i--) + { + j = rand() % i; + t = a[i - 1]; + a[i - 1] = a[j]; + a[j] = t; + } +} + +/*mathematical mod without negative numbers*/ +int mod(int a, int b) +{ + int c = a % b; + if (c < 0) + c += b; + return c; +} + +/* Produce a uniformly random matching of [0..n-1] (each entry pairs with + * exactly one other entry). Algorithm: shuffle the index list, then pair + * adjacent elements. This is provably uniform over the set of perfect + * matchings (and far simpler than the prior slot-counting version). + * + * For odd n the last element targets itself (it has no partner). + */ +void random_pairs(int *a, int n) +{ + int i; + int has_self = (n % 2 == 1); + int pair_n = has_self ? n - 1 : n; + int *shuf = (int *)malloc(sizeof(int) * n); + + if (shuf == NULL) { + fprintf(stderr, "random_pairs: malloc failed\n"); + MPI_Abort(MPI_COMM_WORLD, -1); + } + for (i = 0; i < n; i++) shuf[i] = i; + permute(shuf, n); + + for (i = 0; i < n; i++) a[i] = -1; + + for (i = 0; i < pair_n; i += 2) { + int x = shuf[i]; + int y = shuf[i + 1]; + a[x] = y; + a[y] = x; + } + if (has_self) { + /* odd n: one rank has no partner — target itself */ + int lone = shuf[n - 1]; + a[lone] = lone; + } + free(shuf); +} + +/* Produce fixed-offset pairs: walks i from 0 upward and pairs i with (i+o) + * whenever both ranks are still free and the partner is not a wrap-around + * (n - i >= o). This guarantees disjoint pairs (i, i+o), (i+2o, i+3o), … + * — for offset o=1 you get (0,1) (2,3) (4,5)…, for o=2 you get (0,2) + * (1,3) (4,6) (5,7)…, etc. + * + * Only non-wrapping reciprocal pairs (i, i+o) are formed; any rank that cannot + * be matched without wrapping around falls through to a self-pair (a[i] = i, + * i.e. it performs no communication). This happens for every offset > n/2, and + * also for many offsets <= n/2 when n is not a multiple of 2*o (e.g. n=12, o=4 + * leaves ranks 8..11 self-paired). Full disjoint tiling is only guaranteed when + * 2*o divides n (notably o=1). Recommended usage: 1 <= o <= n/2. + */ +void offset_pairs(int *a, int n, int o) +{ + int i, t; + for (i = 0; i < n; i++) + a[i] = -1; + for (i = 0; i < n; i++) + { + t = mod(i + o, n); + if (a[i] == -1 && a[t] == -1) + { + if (n - i >= o) + { + a[i] = t; + a[t] = i; + } + } + } + for (i = 0; i < n; i++) + { + if (a[i] == -1) + a[i] = i; /* no reciprocal partner — self */ + } +} + +void* malloc_align(size_t size) +{ + void *p = NULL; + int ret = posix_memalign(&p, ALIGNMENT, size); + if (ret != 0) { + fprintf(stderr, "Failed to allocate memory on rank\n"); + MPI_Abort(MPI_COMM_WORLD, -1); + } + return p; +} diff --git a/src/common.h b/src/common.h index 18a98038c..77db72048 100644 --- a/src/common.h +++ b/src/common.h @@ -1,3 +1,12 @@ +#ifndef BLINK_COMMON_H +#define BLINK_COMMON_H + +/* Shared Blink runtime: declarations only. Definitions live in common.c, which + * is compiled once into the `blink_common` library and linked into every + * benchmark. (Previously this was a header full of `static inline` functions + * instantiated per translation unit — cleaner as a single compilation unit, and + * it gives accurate gcov coverage instead of per-TU copies gcov can't merge.) */ + #include #include #include @@ -9,734 +18,75 @@ #include #include -/* ── random duration samplers ────────────────────────────────────────────── */ - -/* Exponential: shape parameter accepted for API uniformity but not used — - * the distribution is fully determined by its mean. */ -static inline double rand_exp(double mean, double shape) -{ - (void)shape; - double u = (rand() + 0.5) / (RAND_MAX + 1.0); /* shift away from 0 and 1 */ - return -mean * log(1.0 - u); -} - -/* Pareto: shape = tail exponent α. Must be > 1 for a finite mean. - * Parameterised so that E[X] = mean regardless of α. - * Inverse-CDF method: x = x_m * u^(-1/α), u ~ Uniform(0,1). */ -static inline double rand_pareto(double mean, double shape) -{ - double alpha = shape; - if (alpha <= 1.0) { - fprintf(stderr, "rand_pareto: shape (alpha) must be > 1, got %g\n", alpha); - exit(-1); - } - if (mean <= 0.0) { - fprintf(stderr, "rand_pareto: mean must be > 0, got %g\n", mean); - exit(-1); - } - double x_m = mean * (alpha - 1.0) / alpha; - double u = (rand() + 0.5) / (RAND_MAX + 1.0); /* shift away from 0 */ - return x_m * pow(u, -1.0 / alpha); -} - -/* Log-normal: shape = σ of the underlying normal. - * Parameterised so that E[X] = mean regardless of σ. - * Box-Muller transform. */ -static inline double rand_lognormal(double mean, double sigma) -{ - if (mean <= 0.0) { - fprintf(stderr, "rand_lognormal: mean must be > 0, got %g\n", mean); - exit(-1); - } - if (sigma <= 0.0) { - fprintf(stderr, "rand_lognormal: sigma must be > 0, got %g\n", sigma); - exit(-1); - } - double mu = log(mean) - 0.5 * sigma * sigma; - double u1 = (rand() + 0.5) / (RAND_MAX + 1.0); /* shift away from 0 */ - double u2 = (rand() + 0.5) / (RAND_MAX + 1.0); - double z = sqrt(-2.0 * log(u1)) * cos(2.0 * M_PI * u2); - return exp(mu + sigma * z); -} - -/* Dispatch to the selected sampler. */ -static inline double rand_duration(double mean, const char *dist, double shape) -{ - if (strcmp(dist, "pareto") == 0) return rand_pareto(mean, shape); - if (strcmp(dist, "lognormal") == 0) return rand_lognormal(mean, shape); - return rand_exp(mean, shape); -} - -/*sleep seconds given as double*/ -static inline int dsleep(double t) -{ - struct timespec t1, t2; - t1.tv_sec = (long)t; - t1.tv_nsec = (t - t1.tv_sec) * 1000000000L; - return nanosleep(&t1, &t2); -} - -/*double comparison function for quicksort*/ -static inline int compare_doubles(const void *p1, const void *p2) -{ - if (*(double *)p1 < *(double *)p2) - return -1; - else if (*(double *)p1 > *(double *)p2) - return 1; - else - return 0; -} - -/* ── globals (shared by signal handler and measurement loop) ─────────────── */ -static int my_rank; -static int w_size; -static int master_rank = 0; -static long long curr_iters; /* total outer iterations executed (warmup + measured) */ -static long long measured_iters; /* number of recorded samples (excludes warmup) */ -static int warm_up_iters = 5; -static int max_samples = 1000; -static double *durations; - -/* Async-signal-safe shutdown flag set by sig_handler; checked in measurement - * loops. The signal handler does no MPI / no malloc / no stdio — only sets - * this flag. The main thread observes it between iterations and exits the - * loop cleanly, then calls write_results() + MPI_Finalize() itself. */ -static volatile sig_atomic_t shutdown_requested = 0; - -/* common benchmark parameters – initialised to defaults, set by parse_common_args() */ -static int msg_size = 1024; -static int measure_granularity = 1; -static int rand_seed = 1; -static int max_iters = 1; -static int endless = 0; -static double burst_length = 0.0; -static int burst_length_rand = 0; /* set by -bldist */ -static double burst_pause = 0.0; -static int burst_pause_rand = 0; /* set by -bpdist */ -static int pretty_output = 0; -static int debug_mode = 0; - -/* runtime-distribution plot (-plot). plot_stat selects which per-iteration - * cross-rank reduction is histogrammed; plot_bin_size (seconds, > 0) overrides - * plot_bins with a fixed bin width; plot_log selects geometric bins. */ -static int plot_output = 0; -static char plot_stat[16] = "max"; /* avg | min | max | median | mainrank */ -static int plot_bins = 10; -static double plot_bin_size = 0.0; /* 0 => use plot_bins */ -static int plot_log = 0; - -/* distribution selection for burst length and pause (-bldist / -bpdist). - * Values: "exp", "pareto", "lognormal". shape: α for Pareto, σ for log-normal - * (ignored for exp). Defaults give exponential with shape unused. */ -static char burst_dist[16] = "exp"; -static double burst_shape = 1.5; -static char pause_dist[16] = "exp"; -static double pause_shape = 1.5; - -/* Bounds-checked accessor for a flag's value argument. Aborts with a clear - * message instead of dereferencing argv[argc] (== NULL) when a value-taking - * flag is supplied as the final command-line token. */ -static inline const char *arg_value(int argc, char **argv, int *i) -{ - if (*i + 1 >= argc) { - if (my_rank == master_rank) - fprintf(stderr, "Missing value for option %s\n", argv[*i]); - MPI_Abort(MPI_COMM_WORLD, -1); - } - return argv[++(*i)]; -} - -/* Validate a distribution name / shape pair selected via -bldist/-bpdist so a - * misconfiguration fails loudly at startup rather than silently producing - * garbage durations later (e.g. log-normal with a non-positive mean). */ -static inline void validate_burst_dist(const char *what, const char *dist, - double mean, double shape) -{ - if (mean <= 0.0) { - if (my_rank == master_rank) - fprintf(stderr, "%s: randomised distribution requires a positive mean, got %g\n", - what, mean); - MPI_Abort(MPI_COMM_WORLD, -1); - } - if (strcmp(dist, "pareto") == 0 && shape <= 1.0) { - if (my_rank == master_rank) - fprintf(stderr, "%s: pareto shape (alpha) must be > 1, got %g\n", what, shape); - MPI_Abort(MPI_COMM_WORLD, -1); - } - if (strcmp(dist, "lognormal") == 0 && shape <= 0.0) { - if (my_rank == master_rank) - fprintf(stderr, "%s: lognormal shape (sigma) must be > 0, got %g\n", what, shape); - MPI_Abort(MPI_COMM_WORLD, -1); - } -} - -/* Parse a duration: a number with an optional unit suffix (s, ms, us, ns). - * A bare number is interpreted as seconds (consistent with -blength/-bpause). - * Returns the value in seconds, or NAN on a malformed string / unknown unit. */ -static inline double parse_duration(const char *s) -{ - char *end = NULL; - double v = strtod(s, &end); - if (end == s) return NAN; /* no leading number */ - while (*end == ' ') end++; - if (*end == '\0') return v; /* bare number => seconds */ - if (strcmp(end, "s") == 0) return v; - if (strcmp(end, "ms") == 0) return v * 1e-3; - if (strcmp(end, "us") == 0) return v * 1e-6; - if (strcmp(end, "ns") == 0) return v * 1e-9; - return NAN; /* unknown unit */ -} - -/* - * Parse the standard set of command-line flags shared by every benchmark. - * Unrecognised flags are compacted to the front of argv (after argv[0]) and - * the new argc is returned, so each benchmark can do a second pass for its - * own flags. Also seeds the RNG and resolves -mrand after parsing. - */ -static inline int parse_common_args(int argc, char **argv) -{ - int new_argc = 1; /* always keep argv[0] (program name) */ - int do_rand_master = 0; - int i; - - for (i = 1; i < argc; i++) { - if (strcmp(argv[i], "-mrank") == 0) { master_rank = atoi(arg_value(argc, argv, &i)); } - else if (strcmp(argv[i], "-mrand") == 0) { do_rand_master = 1; } - else if (strcmp(argv[i], "-msgsize") == 0) { msg_size = atoi(arg_value(argc, argv, &i)); } - else if (strcmp(argv[i], "-endl") == 0) { endless = 1; } - else if (strcmp(argv[i], "-iter") == 0) { max_iters = atoi(arg_value(argc, argv, &i)); } - else if (strcmp(argv[i], "-warmup") == 0) { warm_up_iters = atoi(arg_value(argc, argv, &i)); } - else if (strcmp(argv[i], "-blength") == 0) { burst_length = atof(arg_value(argc, argv, &i)); } - else if (strcmp(argv[i], "-bpause") == 0) { burst_pause = atof(arg_value(argc, argv, &i)); } - else if (strcmp(argv[i], "-bldist") == 0) { strncpy(burst_dist, arg_value(argc, argv, &i), 15); - burst_dist[15] = '\0'; - burst_length_rand = 1; } - else if (strcmp(argv[i], "-bpdist") == 0) { strncpy(pause_dist, arg_value(argc, argv, &i), 15); - pause_dist[15] = '\0'; - burst_pause_rand = 1; } - else if (strcmp(argv[i], "-blshape") == 0) { burst_shape = atof(arg_value(argc, argv, &i)); } - else if (strcmp(argv[i], "-bpshape") == 0) { pause_shape = atof(arg_value(argc, argv, &i)); } - else if (strcmp(argv[i], "-seed") == 0) { rand_seed = atoi(arg_value(argc, argv, &i)); } - else if (strcmp(argv[i], "-grty") == 0) { measure_granularity = atoi(arg_value(argc, argv, &i)); } - else if (strcmp(argv[i], "-maxsamples") == 0) { max_samples = atoi(arg_value(argc, argv, &i)); } - else if (strcmp(argv[i], "-pretty-print") == 0) { pretty_output = 1; } - else if (strcmp(argv[i], "-debug") == 0) { debug_mode = 1; } - else if (strcmp(argv[i], "-plot") == 0) { plot_output = 1; } - else if (strcmp(argv[i], "-plotstat") == 0) { strncpy(plot_stat, arg_value(argc, argv, &i), 15); - plot_stat[15] = '\0'; } - else if (strcmp(argv[i], "-plotbins") == 0) { plot_bins = atoi(arg_value(argc, argv, &i)); } - else if (strcmp(argv[i], "-plotlog") == 0) { plot_log = 1; } - else if (strcmp(argv[i], "-plotbinsize") == 0) { const char *_bs = arg_value(argc, argv, &i); - plot_bin_size = parse_duration(_bs); - if (isnan(plot_bin_size) || plot_bin_size <= 0.0) { - if (my_rank == master_rank) - fprintf(stderr, "-plotbinsize must be a positive duration " - "(e.g. 2ms, 500us, 0.001), got '%s'\n", _bs); - MPI_Abort(MPI_COMM_WORLD, -1); - } } - else { argv[new_argc++] = argv[i]; } /* pass through unknown flags */ - } - - /* fail fast on nonsensical numeric parameters */ - if (max_samples < 1) { - if (my_rank == master_rank) - fprintf(stderr, "-maxsamples must be >= 1, got %d\n", max_samples); - MPI_Abort(MPI_COMM_WORLD, -1); - } - if (measure_granularity < 1) { - if (my_rank == master_rank) - fprintf(stderr, "-grty must be >= 1, got %d\n", measure_granularity); - MPI_Abort(MPI_COMM_WORLD, -1); - } - - /* a randomised burst/pause distribution needs a positive mean and a valid - * shape — validate now so we never feed log(0)/negative values to a sampler */ - if (burst_length_rand) - validate_burst_dist("-bldist", burst_dist, burst_length, burst_shape); - if (burst_pause_rand) - validate_burst_dist("-bpdist", pause_dist, burst_pause, pause_shape); - - /* seed RNG (shared across all ranks) and resolve randomised master */ - srand(rand_seed); - if (do_rand_master) - master_rank = rand() % w_size; - - /* validate the resolved master rank is a real rank. An out-of-range -mrank - * would otherwise become an invalid root in MPI_Gather/Bcast/Reduce, and can - * deadlock benchmarks that branch on (my_rank == master_rank). Gate the - * message on rank 0 because master_rank itself may be the bad value. */ - if (master_rank < 0 || master_rank >= w_size) { - if (my_rank == 0) - fprintf(stderr, "-mrank must be in [0, %d), got %d\n", w_size, master_rank); - MPI_Abort(MPI_COMM_WORLD, -1); - } - - /* validate -plot options */ - if (plot_output) { - if (strcmp(plot_stat, "avg") && strcmp(plot_stat, "min") && strcmp(plot_stat, "max") - && strcmp(plot_stat, "median") && strcmp(plot_stat, "mainrank")) { - if (my_rank == master_rank) - fprintf(stderr, "-plotstat must be one of avg|min|max|median|mainrank, got %s\n", plot_stat); - MPI_Abort(MPI_COMM_WORLD, -1); - } - if (plot_bin_size > 0.0 && plot_log) { - if (my_rank == master_rank) - fprintf(stderr, "-plotbinsize cannot be combined with -plotlog " - "(a constant width is meaningless on a log axis)\n"); - MPI_Abort(MPI_COMM_WORLD, -1); - } - if (plot_bin_size <= 0.0 && plot_bins < 1) { - if (my_rank == master_rank) - fprintf(stderr, "-plotbins must be >= 1, got %d\n", plot_bins); - MPI_Abort(MPI_COMM_WORLD, -1); - } - } - - return new_argc; -} - -/* Convenience wrappers used by benchmark measurement loops. */ -static inline double sample_burst_length(double mean) { return rand_duration(mean, burst_dist, burst_shape); } -static inline double sample_pause_length(double mean) { return rand_duration(mean, pause_dist, pause_shape); } - -/* Record a single measured-iteration latency into the ring buffer. - * Skips writes during warm-up: callers gate this by `if (k >= warm_up_iters)` - * at the outer iteration level — see the standard benchmark loop. - * - * The ring buffer of size `max_samples` therefore only ever holds measured - * samples (no warmup pollution); the (oldest → newest) ordering is - * (measured_iters % max_samples) ... (measured_iters - 1) % max_samples. */ -static inline void record_duration(double t) -{ - durations[measured_iters % max_samples] = t; - measured_iters++; -} - -/* ── output helpers ──────────────────────────────────────────────────────── */ - -/*format a duration (seconds) with auto-scaled units; "%9.2f UU" is normally - * 12 chars wide (9 is a minimum field width, so extreme outliers can exceed it)*/ -static inline void format_duration(char *buf, size_t len, double t) -{ - if (t < 1e-3) - snprintf(buf, len, "%9.2f us", t * 1e6); - else if (t < 1.0) - snprintf(buf, len, "%9.2f ms", t * 1e3); - else - snprintf(buf, len, "%9.2f s", t); -} - -/* Strip the leading spaces format_duration adds (it right-pads to %9.2f). */ -static inline const char *ltrim_spaces(const char *s) -{ - while (*s == ' ') s++; - return s; -} - -#ifndef BLINK_PLOT_MAX_BINS -#define BLINK_PLOT_MAX_BINS 64 +#ifdef __cplusplus +extern "C" { /* common.c is C; let the C++ (comm_only) benchmarks link to it */ #endif -#define BLINK_PLOT_BAR_MAX 40 -/* Print one histogram row: an aligned [lo - hi] range label, a bar scaled to - * the tallest bin (`maxc`), the empirical percentage, and — when `theory` >= 0 - * — a theoretical percentage alongside it (dist_test's CDF overlay). An - * infinite `hi` is rendered as "inf" for an open-ended overflow bin. Shared by - * the -plot histogram and dist_test's sampler-verification histogram. */ -static inline void print_histogram_row(double lo, double hi, int count, int maxc, - int n, double theory) -{ - char lo_s[24], hi_s[24]; - int k, bar; - format_duration(lo_s, sizeof(lo_s), lo); - if (isinf(hi)) snprintf(hi_s, sizeof(hi_s), " %8s", "inf"); - else format_duration(hi_s, sizeof(hi_s), hi); - bar = (maxc > 0) ? (int)floor((double)count / maxc * BLINK_PLOT_BAR_MAX + 0.5) : 0; - printf(" [%s - %s] ", lo_s, hi_s); - for (k = 0; k < bar; k++) printf("\xe2\x96\x88"); /* full block */ - for (k = bar; k < BLINK_PLOT_BAR_MAX; k++) printf(" "); - if (theory >= 0.0) printf(" %5.1f%% (theory %5.1f%%)\n", 100.0 * count / n, 100.0 * theory); - else printf(" %5.1f%%\n", 100.0 * count / n); -} - -/* Render an ASCII histogram of `n` per-iteration timing samples (seconds), - * followed by a percentile footer. Binning modes: - * logscale : `bins` geometric bins between min and max (needs min > 0); - * bin_size > 0 : fixed-width linear bins, lower edge aligned to a multiple of - * bin_size, capped at BLINK_PLOT_MAX_BINS with the tail folded - * into the last bin (a note is printed when this triggers); - * otherwise : `bins` equal-width linear bins between min and max. - * `vals` is sorted in place — pass a scratch array you no longer need. */ -static inline void print_runtime_histogram(double *vals, int n, const char *statname, - int bins, double bin_size, int logscale) -{ - int i; - printf("\n"); - if (n <= 0) { printf(" (-plot: no measured samples to plot)\n"); return; } - - qsort(vals, n, sizeof(double), compare_doubles); - double vmin = vals[0], vmax = vals[n - 1]; - - double sum = 0.0; - for (i = 0; i < n; i++) sum += vals[i]; - - if (vmax <= vmin) { /* degenerate: all identical */ - char one[24]; - format_duration(one, sizeof(one), vmin); - printf("\033[1m\033[36m Per-iteration latency \xc2\xb7 stat=%s \xc2\xb7 n=%d\033[0m\n", - statname, n); - printf(" all samples = %s\n", ltrim_spaces(one)); - return; - } - - int use_log = (logscale && vmin > 0.0); - int nb; - int overflow = 0; - double lo0 = vmin, width = 0.0, ratio = 1.0; - - if (use_log) { - nb = bins > 0 ? bins : 10; - ratio = pow(vmax / vmin, 1.0 / nb); - } else if (bin_size > 0.0) { - lo0 = floor(vmin / bin_size) * bin_size; - long need = (long)floor((vmax - lo0) / bin_size) + 1; - if (need < 1) need = 1; - if (need > BLINK_PLOT_MAX_BINS) { nb = BLINK_PLOT_MAX_BINS; overflow = 1; } - else { nb = (int)need; } - width = bin_size; - } else { - nb = bins > 0 ? bins : 10; - width = (vmax - vmin) / nb; - } - if (nb < 1) nb = 1; - - int *counts = (int *)calloc((size_t)nb, sizeof(int)); - if (!counts) { printf(" (-plot: allocation failed)\n"); return; } - for (i = 0; i < n; i++) { - int b = use_log ? (int)floor(log(vals[i] / vmin) / log(ratio)) - : (int)floor((vals[i] - lo0) / width); - if (b < 0) b = 0; - if (b >= nb) b = nb - 1; /* fold any overflow into the last bin */ - counts[b]++; - } - int maxc = 1; - for (i = 0; i < nb; i++) if (counts[i] > maxc) maxc = counts[i]; - - printf("\033[1m\033[36m Per-iteration latency \xc2\xb7 stat=%s \xc2\xb7 n=%d \xc2\xb7 %s\033[0m\n", - statname, n, use_log ? "log bins" : (bin_size > 0.0 ? "fixed bins" : "linear bins")); - if (overflow) - printf("\033[33m note: -plotbinsize would need %ld bins for the observed range; " - "capped at %d (tail folded into the last bin)\033[0m\n", - (long)floor((vmax - lo0) / bin_size) + 1, BLINK_PLOT_MAX_BINS); - printf("\033[2m ----------------------------------------------------------------------\033[0m\n"); - - for (i = 0; i < nb; i++) { - double lo = use_log ? vmin * pow(ratio, i) : lo0 + i * width; - double hi = use_log ? vmin * pow(ratio, i + 1) : lo0 + (i + 1) * width; - print_histogram_row(lo, hi, counts[i], maxc, n, -1.0); /* -1 => no theory overlay */ - } - printf("\033[2m ----------------------------------------------------------------------\033[0m\n"); - - char s_min[24], s_mean[24], s_p50[24], s_p90[24], s_p99[24], s_max[24]; - format_duration(s_min, sizeof(s_min), vmin); - format_duration(s_mean, sizeof(s_mean), sum / n); - format_duration(s_p50, sizeof(s_p50), vals[(int)(0.50 * (n - 1) + 0.5)]); - format_duration(s_p90, sizeof(s_p90), vals[(int)(0.90 * (n - 1) + 0.5)]); - format_duration(s_p99, sizeof(s_p99), vals[(int)(0.99 * (n - 1) + 0.5)]); - format_duration(s_max, sizeof(s_max), vmax); - printf(" n=%d \xc2\xb7 min %s \xc2\xb7 mean %s \xc2\xb7 p50 %s \xc2\xb7 p90 %s \xc2\xb7 p99 %s \xc2\xb7 max %s\n", - n, ltrim_spaces(s_min), ltrim_spaces(s_mean), ltrim_spaces(s_p50), - ltrim_spaces(s_p90), ltrim_spaces(s_p99), ltrim_spaces(s_max)); - free(counts); -} - -static inline void write_results() -{ - double duration_sum; - double duration_median; - int num_samples; - int i; - int start_index; - double *tmp_buf = NULL; - - if (measured_iters > max_samples) /* wrapped the sampling ring buffer */ - { - num_samples = max_samples; - start_index = (int)(measured_iters % max_samples); - } - else - { - num_samples = (int)measured_iters; - start_index = 0; - } - - /* MPI_Gather below uses num_samples as BOTH send- and recv-count, which is - * only valid if every rank contributes the same count. Ranks normally stay - * in lockstep, but to be robust (e.g. a SIGUSR1 shutdown that reaches ranks - * at slightly different iterations) collectively agree on the common minimum - * so the gather can never mismatch. */ - { - int common_samples = num_samples; - MPI_Allreduce(&num_samples, &common_samples, 1, MPI_INT, MPI_MIN, MPI_COMM_WORLD); - /* keep only the most recent common_samples (drop the oldest extras) */ - start_index += (num_samples - common_samples); - num_samples = common_samples; - } - - tmp_buf = (double *)malloc(sizeof(double) * (num_samples > 0 ? num_samples : 1)); - /* copy in chronological order (oldest → newest) using circular indexing */ - for (i = 0; i < num_samples; i++) { - tmp_buf[i] = durations[(start_index + i) % max_samples]; - } - - double *all_data = (double *)malloc(sizeof(double)*(num_samples > 0 ? num_samples : 1)*w_size); - double *sorting_buf = (double *)malloc(sizeof(double)*w_size); - - if (all_data == NULL || sorting_buf == NULL) { - fprintf(stderr, "Failed to allocate a buffer on rank %d\n", my_rank); - MPI_Abort(MPI_COMM_WORLD, -1); - } - - /* print header before the gather so it appears first */ - if (my_rank == master_rank) { - if (pretty_output) { - printf("\033[1m\033[36m" - " %5s %12s %12s %12s %12s" - "\033[0m\n", - "#", "avg", "min", "max", "median"); - printf("\033[2m" - " -------------------------------------------------------------" - "\033[0m\n"); - } else { - printf("Average,Minimum,Maximum,Median,MainRank\n"); - } - } - - MPI_Gather(tmp_buf, num_samples, MPI_DOUBLE, - all_data, num_samples, MPI_DOUBLE, - master_rank, MPI_COMM_WORLD); - - if (my_rank == master_rank) { - /* -plot: capture one chosen cross-rank statistic per iteration */ - int plot_stat_id = 0; /* 0=avg 1=min 2=max 3=median 4=mainrank */ - double *plot_vals = NULL; - if (plot_output) { - if (strcmp(plot_stat, "min") == 0) plot_stat_id = 1; - else if (strcmp(plot_stat, "max") == 0) plot_stat_id = 2; - else if (strcmp(plot_stat, "median") == 0) plot_stat_id = 3; - else if (strcmp(plot_stat, "mainrank") == 0) plot_stat_id = 4; - plot_vals = (double *)malloc(sizeof(double) * (num_samples > 0 ? num_samples : 1)); - if (plot_vals == NULL) { - fprintf(stderr, "Failed to allocate plot buffer on rank %d\n", my_rank); - MPI_Abort(MPI_COMM_WORLD, -1); - } - } - for (i = 0; i < num_samples; i++) { - int j; - duration_sum = 0; - for (j = 0; j < w_size; j++) { - sorting_buf[j] = all_data[j*num_samples + i]; - duration_sum += sorting_buf[j]; - } - - qsort(sorting_buf, w_size, sizeof(double), compare_doubles); - if (w_size % 2 == 0) /* even: median as mean of two middle values */ - duration_median = (sorting_buf[(w_size-1)/2] + sorting_buf[w_size/2]) / 2.0; - else /* odd: median is the middle value */ - duration_median = sorting_buf[(w_size-1)/2]; - - if (plot_output) { - switch (plot_stat_id) { - case 1: plot_vals[i] = sorting_buf[0]; break; - case 2: plot_vals[i] = sorting_buf[w_size - 1]; break; - case 3: plot_vals[i] = duration_median; break; - case 4: plot_vals[i] = tmp_buf[i]; break; - default: plot_vals[i] = duration_sum / w_size; break; - } - } - - if (pretty_output) { - char avg_s[24], min_s[24], max_s[24], med_s[24]; - format_duration(avg_s, sizeof(avg_s), duration_sum / w_size); - format_duration(min_s, sizeof(min_s), sorting_buf[0]); - format_duration(max_s, sizeof(max_s), sorting_buf[w_size-1]); - format_duration(med_s, sizeof(med_s), duration_median); - printf(" %5d %12s %12s %12s %12s\n", - i+1, avg_s, min_s, max_s, med_s); - } else { - printf("%.9f,%.9f,%.9f,%.9f,%.9f\n", - duration_sum / w_size, - sorting_buf[0], sorting_buf[w_size-1], - duration_median, tmp_buf[i]); - } - } - - if (pretty_output) { - printf("\033[2m" - " -------------------------------------------------------------" - "\033[0m\n"); - printf(" %d samples · %lld iterations total\n", num_samples, curr_iters); - } else { - printf("Ran %lld iterations. Measured %d iterations.\n", curr_iters, num_samples); - } - - /* -plot: additive ASCII histogram of the chosen per-iteration statistic */ - if (plot_output) { - print_runtime_histogram(plot_vals, num_samples, plot_stat, - plot_bins, plot_bin_size, plot_log); - free(plot_vals); - } - - fflush(stdout); - } - - free(sorting_buf); - free(all_data); - free(tmp_buf); -} - -/* Async-signal-safe handler: only sets a flag. The measurement loop in each - * benchmark observes the flag and exits cleanly; write_results() and - * MPI_Finalize() happen on the main thread, never from signal context. */ -static inline void sig_handler(int sig) -{ - (void)sig; - shutdown_requested = 1; -} - -/* Install the SIGUSR1 shutdown handler with sigaction() rather than signal(), - * for portable, persistent (non-one-shot) semantics across platforms. */ -static inline void install_shutdown_handler(void) -{ - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = sig_handler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - sigaction(SIGUSR1, &sa, NULL); -} - -/* Collectively decide whether to shut down. SIGUSR1 only sets the flag on the - * rank that received it, so a purely local check would make ranks leave the - * measurement loop at different iterations — desynchronising the per-iteration - * collectives and the final MPI_Gather (deadlock / mismatch). This all-reduce - * (called once per outer iteration, OUTSIDE the timed region) guarantees every - * rank breaks on the same iteration. Returns non-zero if any rank requested - * shutdown. */ -static inline int check_shutdown(void) -{ - int local = (int)shutdown_requested; - int global = 0; - MPI_Allreduce(&local, &global, 1, MPI_INT, MPI_MAX, MPI_COMM_WORLD); - return global; -} - -/* ── combinatorics helpers ───────────────────────────────────────────────── */ - -/*use Fisher-Yates to permute array*/ -static inline void permute(int *a, int n) -{ - int j, t, i; - for (i = n; i > 1; i--) - { - j = rand() % i; - t = a[i - 1]; - a[i - 1] = a[j]; - a[j] = t; - } -} - -/*mathematical mod without negative numbers*/ -static inline int mod(int a, int b) -{ - int c = a % b; - if (c < 0) - c += b; - return c; -} - -/* Produce a uniformly random matching of [0..n-1] (each entry pairs with - * exactly one other entry). Algorithm: shuffle the index list, then pair - * adjacent elements. This is provably uniform over the set of perfect - * matchings (and far simpler than the prior slot-counting version). - * - * For odd n the last element targets itself (it has no partner). - */ -static inline void random_pairs(int *a, int n) -{ - int i; - int has_self = (n % 2 == 1); - int pair_n = has_self ? n - 1 : n; - int *shuf = (int *)malloc(sizeof(int) * n); - - if (shuf == NULL) { - fprintf(stderr, "random_pairs: malloc failed\n"); - MPI_Abort(MPI_COMM_WORLD, -1); - } - for (i = 0; i < n; i++) shuf[i] = i; - permute(shuf, n); - - for (i = 0; i < n; i++) a[i] = -1; - - for (i = 0; i < pair_n; i += 2) { - int x = shuf[i]; - int y = shuf[i + 1]; - a[x] = y; - a[y] = x; - } - if (has_self) { - /* odd n: one rank has no partner — target itself */ - int lone = shuf[n - 1]; - a[lone] = lone; - } - free(shuf); -} - -/* Produce fixed-offset pairs: walks i from 0 upward and pairs i with (i+o) - * whenever both ranks are still free and the partner is not a wrap-around - * (n - i >= o). This guarantees disjoint pairs (i, i+o), (i+2o, i+3o), … - * — for offset o=1 you get (0,1) (2,3) (4,5)…, for o=2 you get (0,2) - * (1,3) (4,6) (5,7)…, etc. - * - * Only non-wrapping reciprocal pairs (i, i+o) are formed; any rank that cannot - * be matched without wrapping around falls through to a self-pair (a[i] = i, - * i.e. it performs no communication). This happens for every offset > n/2, and - * also for many offsets <= n/2 when n is not a multiple of 2*o (e.g. n=12, o=4 - * leaves ranks 8..11 self-paired). Full disjoint tiling is only guaranteed when - * 2*o divides n (notably o=1). Recommended usage: 1 <= o <= n/2. - */ -static inline void offset_pairs(int *a, int n, int o) -{ - int i, t; - for (i = 0; i < n; i++) - a[i] = -1; - for (i = 0; i < n; i++) - { - t = mod(i + o, n); - if (a[i] == -1 && a[t] == -1) - { - if (n - i >= o) - { - a[i] = t; - a[t] = i; - } - } - } - for (i = 0; i < n; i++) - { - if (a[i] == -1) - a[i] = i; /* no reciprocal partner — self */ - } +/* ── globals (defined in common.c) ───────────────────────────────────────── */ +extern int my_rank; +extern int w_size; +extern int master_rank; +extern long long curr_iters; /* total outer iterations (warmup + measured) */ +extern long long measured_iters; /* recorded samples (excludes warmup) */ +extern int warm_up_iters; +extern int max_samples; +extern double *durations; +extern volatile sig_atomic_t shutdown_requested; + +extern int msg_size; +extern int measure_granularity; +extern int rand_seed; +extern int max_iters; +extern int endless; +extern double burst_length; +extern int burst_length_rand; /* set by -bldist */ +extern double burst_pause; +extern int burst_pause_rand; /* set by -bpdist */ +extern int pretty_output; +extern int debug_mode; + +extern int plot_output; /* -plot */ +extern char plot_stat[16]; /* avg | min | max | median | mainrank */ +extern int plot_bins; +extern double plot_bin_size; /* 0 => use plot_bins */ +extern int plot_log; + +extern char burst_dist[16]; /* "exp" | "pareto" | "lognormal" */ +extern double burst_shape; +extern char pause_dist[16]; +extern double pause_shape; + +/* ── functions (defined in common.c) ─────────────────────────────────────── */ +double rand_exp(double mean, double shape); +double rand_pareto(double mean, double shape); +double rand_lognormal(double mean, double sigma); +double rand_duration(double mean, const char *dist, double shape); +int dsleep(double t); +int compare_doubles(const void *p1, const void *p2); +const char *arg_value(int argc, char **argv, int *i); +void validate_burst_dist(const char *what, const char *dist, double mean, double shape); +double parse_duration(const char *s); +int parse_common_args(int argc, char **argv); +double sample_burst_length(double mean); +double sample_pause_length(double mean); +void record_duration(double t); +void format_duration(char *buf, size_t len, double t); +const char *ltrim_spaces(const char *s); +void print_histogram_row(double lo, double hi, int count, int maxc, int n, double theory); +void print_runtime_histogram(double *vals, int n, const char *statname, + int bins, double bin_size, int logscale); +void write_results(void); +void sig_handler(int sig); +void install_shutdown_handler(void); +int check_shutdown(void); +void permute(int *a, int n); +int mod(int a, int b); +void random_pairs(int *a, int n); +void offset_pairs(int *a, int n, int o); +void *malloc_align(size_t size); + +#ifdef __cplusplus } +#endif -#define ALIGNMENT (sysconf(_SC_PAGESIZE)) -static inline void* malloc_align(size_t size) -{ - void *p = NULL; - int ret = posix_memalign(&p, ALIGNMENT, size); - if (ret != 0) { - fprintf(stderr, "Failed to allocate memory on rank\n"); - MPI_Abort(MPI_COMM_WORLD, -1); - } - return p; -} +#endif /* BLINK_COMMON_H */ From 601e4bfa750f1758f7e6100e0e2cc6dd610cb63e Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Mon, 8 Jun 2026 21:24:02 +0200 Subject: [PATCH 21/26] tests: cover randomised-burst/pause branches in every benchmark Add a burstdist case to each suite (collectives via the 2 parameterised fixtures, plus incast/pairwise/ring/pingpong/kpartners/stencil) running -bldist/-bpause/-bpdist, so the if(burst_length_rand) / if(burst_pause!=0) / if(burst_pause_rand) branches are exercised in all ~35 benchmarks (previously only barrier_nb). Lifts src/ coverage to ~92% lines / 100% functions / 72% branches; 8/8 suites pass. Co-Authored-By: Claude Opus 4.8 --- tests/test_collectives.cpp | 14 ++++++++++++++ tests/test_incast.cpp | 30 ++++++++++++++++++++++++++++++ tests/test_kpartners.cpp | 5 +++++ tests/test_pairwise.cpp | 15 +++++++++++++++ tests/test_pingpong.cpp | 11 +++++++++++ tests/test_ring.cpp | 12 ++++++++++++ tests/test_stencil.cpp | 5 +++++ 7 files changed, 92 insertions(+) diff --git a/tests/test_collectives.cpp b/tests/test_collectives.cpp index 3b7205b73..b7c6d6460 100644 --- a/tests/test_collectives.cpp +++ b/tests/test_collectives.cpp @@ -83,6 +83,14 @@ TEST_P(UnrootedCollectiveTest, DataIntegrity_grty) { check_coverage(m, N, GetParam() + " grty=4"); check_all_ok(m, GetParam() + " grty=4"); } +/* Randomised burst length (-bldist) + a pause (-bpause): exercises the + * if(burst_length_rand) / if(burst_pause!=0) / if(burst_pause_rand) branches in + * this benchmark's measurement loop, which the fixed -blength burst test misses. */ +TEST_P(UnrootedCollectiveTest, DataIntegrity_burstdist) { + auto m = run_debug(GetParam(), N, "-iter 3 -blength 0.0004 -bldist exp -bpause 0.0003 -bpdist exp"); + check_coverage(m, N, GetParam() + " burstdist"); + check_all_ok(m, GetParam() + " burstdist"); +} INSTANTIATE_TEST_SUITE_P( Collectives, UnrootedCollectiveTest, @@ -133,6 +141,12 @@ TEST_P(RootedCollectiveTest, DataIntegrity_grty) { check_coverage(m, N, GetParam() + " grty=4"); check_all_ok(m, GetParam() + " grty=4"); } +/* Randomised burst + pause: see the unrooted note. */ +TEST_P(RootedCollectiveTest, DataIntegrity_burstdist) { + auto m = run_debug(GetParam(), N, "-iter 3 -blength 0.0004 -bldist exp -bpause 0.0003 -bpdist exp"); + check_coverage(m, N, GetParam() + " burstdist"); + check_all_ok(m, GetParam() + " burstdist"); +} TEST_P(RootedCollectiveTest, NonDefaultRoot) { auto m = run_debug(GetParam(), N, "-mrank " + std::to_string(ALT)); check_coverage(m, N, GetParam() + " mrank=3"); diff --git a/tests/test_incast.cpp b/tests/test_incast.cpp index 704c97158..7d64143d5 100644 --- a/tests/test_incast.cpp +++ b/tests/test_incast.cpp @@ -86,6 +86,12 @@ TEST_F(IncastBTest, Burst) { check_roles(m, N, MASTER, "incast_b burst"); check_all_ok(m, "incast_b burst"); } +TEST_F(IncastBTest, Burstdist) { + auto m = run_debug("incast_b", N, "-iter 3 -blength 0.0004 -bldist exp -bpause 0.0003 -bpdist exp"); + check_coverage(m, N, "incast_b burstdist"); + check_roles(m, N, MASTER, "incast_b burstdist"); + check_all_ok(m, "incast_b burstdist"); +} TEST_F(IncastBTest, NonDefaultMaster) { const int ALT = 3; auto m = run_debug("incast_b", N, "-mrank " + std::to_string(ALT)); @@ -117,6 +123,12 @@ TEST_F(IncastNbTest, Burst) { check_roles(m, N, MASTER, "incast_nb burst"); check_all_ok(m, "incast_nb burst"); } +TEST_F(IncastNbTest, Burstdist) { + auto m = run_debug("incast_nb", N, "-iter 3 -blength 0.0004 -bldist exp -bpause 0.0003 -bpdist exp"); + check_coverage(m, N, "incast_nb burstdist"); + check_roles(m, N, MASTER, "incast_nb burstdist"); + check_all_ok(m, "incast_nb burstdist"); +} /* -grty>1: the root posts grty*(N-1) concurrent Irecvs, each into a disjoint * slot. A shared slot would be an MPI overlap violation; this guards it. */ TEST_F(IncastNbTest, Grty) { @@ -170,6 +182,12 @@ TEST_F(IncastBsnbrTest, Burst) { check_roles(m, N, MASTER, "incast_bsnbr burst"); check_all_ok(m, "incast_bsnbr burst"); } +TEST_F(IncastBsnbrTest, Burstdist) { + auto m = run_debug("incast_bsnbr", N, "-iter 3 -blength 0.0004 -bldist exp -bpause 0.0003 -bpdist exp"); + check_coverage(m, N, "incast_bsnbr burstdist"); + check_roles(m, N, MASTER, "incast_bsnbr burstdist"); + check_all_ok(m, "incast_bsnbr burstdist"); +} TEST_F(IncastBsnbrTest, NonDefaultMaster) { const int ALT = 3; auto m = run_debug("incast_bsnbr", N, "-mrank " + std::to_string(ALT)); @@ -215,6 +233,12 @@ TEST_F(IncastGetTest, Burst) { check_roles(m, N, MASTER, "incast_get burst"); check_all_ok(m, "incast_get burst"); } +TEST_F(IncastGetTest, Burstdist) { + auto m = run_debug("incast_get", N, "-iter 3 -blength 0.0004 -bldist exp -bpause 0.0003 -bpdist exp"); + check_coverage(m, N, "incast_get burstdist"); + check_roles(m, N, MASTER, "incast_get burstdist"); + check_all_ok(m, "incast_get burstdist"); +} /* -grty>1: the root issues grty Gets per sender into distinct window-offset * slots; guards the RMA displacement math (j*M*grty + i*M). */ TEST_F(IncastGetTest, Grty) { @@ -268,6 +292,12 @@ TEST_F(IncastPutTest, Burst) { check_roles(m, N, MASTER, "incast_put burst"); check_all_ok(m, "incast_put burst"); } +TEST_F(IncastPutTest, Burstdist) { + auto m = run_debug("incast_put", N, "-iter 3 -blength 0.0004 -bldist exp -bpause 0.0003 -bpdist exp"); + check_coverage(m, N, "incast_put burstdist"); + check_roles(m, N, MASTER, "incast_put burstdist"); + check_all_ok(m, "incast_put burstdist"); +} /* -grty>1: each sender issues grty Puts into distinct window-offset slots; * guards the RMA displacement math (rank*M*grty + i*M). */ TEST_F(IncastPutTest, Grty) { diff --git a/tests/test_kpartners.cpp b/tests/test_kpartners.cpp index 4c10fc73c..e94301039 100644 --- a/tests/test_kpartners.cpp +++ b/tests/test_kpartners.cpp @@ -70,3 +70,8 @@ TEST_F(KpartnersTest, DataIntegrity_burst) { check_coverage(m, N, "k=1 burst"); check_all_ok(m, "k=1 burst"); } +TEST_F(KpartnersTest, DataIntegrity_burstdist) { + auto m = run_debug("kpartners_nb", N, "-iter 3 -blength 0.0004 -bldist exp -bpause 0.0003 -bpdist exp"); + check_coverage(m, N, "k=1 burstdist"); + check_all_ok(m, "k=1 burstdist"); +} diff --git a/tests/test_pairwise.cpp b/tests/test_pairwise.cpp index 6d86291b7..b23c6d62f 100644 --- a/tests/test_pairwise.cpp +++ b/tests/test_pairwise.cpp @@ -133,6 +133,11 @@ TEST_F(PairwiseBTest, Burst) { check_symmetric(m, "pairwise_b burst"); check_all_ok(m, "pairwise_b burst"); } +TEST_F(PairwiseBTest, Burstdist) { + auto m = run_debug("pairwise_b", N, "-mode offpair -offset 1 -iter 3 -blength 0.0004 -bldist exp -bpause 0.0003 -bpdist exp"); + check_coverage(m, N, "pairwise_b burstdist"); + check_all_ok(m, "pairwise_b burstdist"); +} /* ── non-reciprocal modes (rot / perm) ────────────────────────────────────── * rot and perm are permutations, not symmetric pairings, so the partner graph @@ -197,6 +202,11 @@ TEST_F(PairwiseNbTest, Burst) { check_symmetric(m, "pairwise_nb burst"); check_all_ok(m, "pairwise_nb burst"); } +TEST_F(PairwiseNbTest, Burstdist) { + auto m = run_debug("pairwise_nb", N, "-mode offpair -offset 1 -iter 3 -blength 0.0004 -bldist exp -bpause 0.0003 -bpdist exp"); + check_coverage(m, N, "pairwise_nb burstdist"); + check_all_ok(m, "pairwise_nb burstdist"); +} TEST_F(PairwiseNbTest, Rot_Offset1_DataIntegrity) { auto m = run_debug("pairwise_nb", N, "-mode rot -offset 1"); check_coverage(m, N, "nb rot offset=1"); @@ -237,6 +247,11 @@ TEST_F(PairwiseBsnbrTest, Burst) { check_symmetric(m, "pairwise_bsnbr burst"); check_all_ok(m, "pairwise_bsnbr burst"); } +TEST_F(PairwiseBsnbrTest, Burstdist) { + auto m = run_debug("pairwise_bsnbr", N, "-mode offpair -offset 1 -iter 3 -blength 0.0004 -bldist exp -bpause 0.0003 -bpdist exp"); + check_coverage(m, N, "pairwise_bsnbr burstdist"); + check_all_ok(m, "pairwise_bsnbr burstdist"); +} TEST_F(PairwiseBsnbrTest, Rot_Offset1_DataIntegrity) { auto m = run_debug("pairwise_bsnbr", N, "-mode rot -offset 1"); check_coverage(m, N, "bsnbr rot offset=1"); diff --git a/tests/test_pingpong.cpp b/tests/test_pingpong.cpp index 5857a6f90..5852afe54 100644 --- a/tests/test_pingpong.cpp +++ b/tests/test_pingpong.cpp @@ -51,6 +51,12 @@ TEST_F(PingpongBTest, DataIntegrity_burst) { check_all_ok(m, "pingpong_b burst"); } +TEST_F(PingpongBTest, DataIntegrity_burstdist) { + auto m = run_debug("pingpong_b", N, "-iter 3 -blength 0.0004 -bldist exp -bpause 0.0003 -bpdist exp"); + check_coverage(m, N, "pingpong_b burstdist"); + check_all_ok(m, "pingpong_b burstdist"); +} + /* ── pingpong_pairwise_b (8 ranks) ─────────────────────────────────────────── */ class PingpongPairwiseBTest : public ::testing::Test { @@ -76,3 +82,8 @@ TEST_F(PingpongPairwiseBTest, DataIntegrity_burst) { check_coverage(m, N, "pingpong_pairwise_b burst"); check_all_ok(m, "pingpong_pairwise_b burst"); } +TEST_F(PingpongPairwiseBTest, DataIntegrity_burstdist) { + auto m = run_debug("pingpong_pairwise_b", N, "-iter 3 -blength 0.0004 -bldist exp -bpause 0.0003 -bpdist exp"); + check_coverage(m, N, "pingpong_pairwise_b burstdist"); + check_all_ok(m, "pingpong_pairwise_b burstdist"); +} diff --git a/tests/test_ring.cpp b/tests/test_ring.cpp index 1fad574ab..ed0ab622c 100644 --- a/tests/test_ring.cpp +++ b/tests/test_ring.cpp @@ -158,6 +158,12 @@ TEST_F(RingNbTest, Burst) { check_consistency(m, "ring_nb burst"); check_all_ok(m, "ring_nb burst"); } +TEST_F(RingNbTest, Burstdist) { + auto m = run_debug("ring_nb", N, "-iter 3 -blength 0.0004 -bldist exp -bpause 0.0003 -bpdist exp"); + check_coverage(m, N, "ring_nb burstdist"); + check_consistency(m, "ring_nb burstdist"); + check_all_ok(m, "ring_nb burstdist"); +} /* Random ring must differ from sequential (sanity: permutation actually happened). */ TEST_F(RingNbTest, Random_DiffersFromSequential) { auto seq = run_debug("ring_nb", N); @@ -219,6 +225,12 @@ TEST_F(RingBsnbrTest, Burst) { check_consistency(m, "ring_bsnbr burst"); check_all_ok(m, "ring_bsnbr burst"); } +TEST_F(RingBsnbrTest, Burstdist) { + auto m = run_debug("ring_bsnbr", N, "-iter 3 -blength 0.0004 -bldist exp -bpause 0.0003 -bpdist exp"); + check_coverage(m, N, "ring_bsnbr burstdist"); + check_consistency(m, "ring_bsnbr burstdist"); + check_all_ok(m, "ring_bsnbr burstdist"); +} TEST_F(RingBsnbrTest, AgreesWith_RingNb_Random) { auto nb = run_debug("ring_nb", N, "-rring -seed 42"); auto bsnbr = run_debug("ring_bsnbr", N, "-rring -seed 42"); diff --git a/tests/test_stencil.cpp b/tests/test_stencil.cpp index 79db31e68..74bd1c912 100644 --- a/tests/test_stencil.cpp +++ b/tests/test_stencil.cpp @@ -109,6 +109,11 @@ TEST_F(StencilTest, DataIntegrity_burst) { check_coverage(m, N, "stencil burst"); check_all_ok(m, "stencil burst"); } +TEST_F(StencilTest, DataIntegrity_burstdist) { + auto m = run_debug("stencil_2d_nb", N, "-iter 3 -blength 0.0004 -bldist exp -bpause 0.0003 -bpdist exp"); + check_coverage(m, N, "stencil burstdist"); + check_all_ok(m, "stencil burstdist"); +} TEST_F(StencilTest, Consistency_burst) { auto m = run_debug("stencil_2d_nb", N, "-blength 0.001"); check_coverage(m, N, "stencil burst"); From e3b39275e71fd163c4f09c4851940d97f0e1f17c Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Wed, 10 Jun 2026 10:12:12 +0200 Subject: [PATCH 22/26] [FIX] Compilation. --- CMakeLists.txt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ddd6887c..c40d160e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,23 @@ cmake_minimum_required(VERSION 3.16) project(blink C CXX) -find_package(MPI REQUIRED COMPONENTS C CXX) +# On HPC systems where CC/CXX are already MPI wrappers (mpicc/mpicxx), +# FindMPI's link-test often fails because LD_LIBRARY_PATH isn't set up for +# CMake's subprocess. Pass -DBLINK_MPI_WRAPPER=ON to skip FindMPI and create +# stub INTERFACE targets instead — the wrapper already injects all flags. +option(BLINK_MPI_WRAPPER + "Assume CC/CXX are MPI compiler wrappers; skip find_package(MPI)" OFF) + +if(BLINK_MPI_WRAPPER) + message(STATUS "BLINK_MPI_WRAPPER=ON — skipping FindMPI, assuming MPI is baked into the compiler") + foreach(_tgt MPI::MPI_C MPI::MPI_CXX) + if(NOT TARGET ${_tgt}) + add_library(${_tgt} INTERFACE IMPORTED) + endif() + endforeach() +else() + find_package(MPI REQUIRED COMPONENTS C CXX) +endif() # All binaries land in /bin/ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) From 55bddc824f4f084d916d818ad2cedcfbc34fe489 Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Wed, 10 Jun 2026 10:15:38 +0200 Subject: [PATCH 23/26] [FIX] Compilation. --- CMakeLists.txt | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c40d160e3..f5c6d4144 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,11 +9,32 @@ option(BLINK_MPI_WRAPPER "Assume CC/CXX are MPI compiler wrappers; skip find_package(MPI)" OFF) if(BLINK_MPI_WRAPPER) - message(STATUS "BLINK_MPI_WRAPPER=ON — skipping FindMPI, assuming MPI is baked into the compiler") + message(STATUS "BLINK_MPI_WRAPPER=ON — querying '${CMAKE_C_COMPILER}' for MPI flags") + execute_process(COMMAND "${CMAKE_C_COMPILER}" --showme:incdirs + OUTPUT_VARIABLE _mpi_incdirs OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) + execute_process(COMMAND "${CMAKE_C_COMPILER}" --showme:libdirs + OUTPUT_VARIABLE _mpi_libdirs OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) + execute_process(COMMAND "${CMAKE_C_COMPILER}" --showme:libs + OUTPUT_VARIABLE _mpi_libs OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) + string(REPLACE " " ";" _mpi_incdirs "${_mpi_incdirs}") + string(REPLACE " " ";" _mpi_libdirs "${_mpi_libdirs}") + string(REPLACE " " ";" _mpi_libs "${_mpi_libs}") + set(_mpi_link_flags "") + foreach(_d ${_mpi_libdirs}) + list(APPEND _mpi_link_flags "-L${_d}") + endforeach() + foreach(_l ${_mpi_libs}) + list(APPEND _mpi_link_flags "-l${_l}") + endforeach() + message(STATUS " MPI includes : ${_mpi_incdirs}") + message(STATUS " MPI link : ${_mpi_link_flags}") foreach(_tgt MPI::MPI_C MPI::MPI_CXX) if(NOT TARGET ${_tgt}) add_library(${_tgt} INTERFACE IMPORTED) endif() + set_target_properties(${_tgt} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_mpi_incdirs}" + INTERFACE_LINK_LIBRARIES "${_mpi_link_flags}") endforeach() else() find_package(MPI REQUIRED COMPONENTS C CXX) From daa9c633c1e1dd5e6a060661ef30b4c8bd5bb865 Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Wed, 10 Jun 2026 10:20:06 +0200 Subject: [PATCH 24/26] [FIX] Compilation. --- CMakeLists.txt | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f5c6d4144..34aba336c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,32 +9,50 @@ option(BLINK_MPI_WRAPPER "Assume CC/CXX are MPI compiler wrappers; skip find_package(MPI)" OFF) if(BLINK_MPI_WRAPPER) - message(STATUS "BLINK_MPI_WRAPPER=ON — querying '${CMAKE_C_COMPILER}' for MPI flags") - execute_process(COMMAND "${CMAKE_C_COMPILER}" --showme:incdirs + # Prefer $ENV{MPICC} (always correct on HPC modules) over CMAKE_C_COMPILER + # (which may be the underlying gcc if the CMake cache is stale). + if(DEFINED ENV{MPICC}) + set(_mpi_cc "$ENV{MPICC}") + elseif(CMAKE_C_COMPILER MATCHES "mpi") + set(_mpi_cc "${CMAKE_C_COMPILER}") + else() + find_program(_mpi_cc NAMES mpicc DOC "MPI C compiler wrapper") + endif() + if(NOT _mpi_cc) + message(FATAL_ERROR + "BLINK_MPI_WRAPPER=ON but no MPI wrapper found.\n" + "Set the MPICC environment variable or put mpicc on PATH.") + endif() + message(STATUS "BLINK_MPI_WRAPPER=ON — querying '${_mpi_cc}' for MPI flags") + execute_process(COMMAND "${_mpi_cc}" --showme:incdirs OUTPUT_VARIABLE _mpi_incdirs OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) - execute_process(COMMAND "${CMAKE_C_COMPILER}" --showme:libdirs + execute_process(COMMAND "${_mpi_cc}" --showme:libdirs OUTPUT_VARIABLE _mpi_libdirs OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) - execute_process(COMMAND "${CMAKE_C_COMPILER}" --showme:libs + execute_process(COMMAND "${_mpi_cc}" --showme:libs OUTPUT_VARIABLE _mpi_libs OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) + if(NOT _mpi_incdirs OR NOT _mpi_libdirs) + message(FATAL_ERROR + "MPI wrapper '${_mpi_cc}' did not return include/lib dirs.\n" + "Check that it supports --showme:incdirs (OpenMPI wrapper required).") + endif() string(REPLACE " " ";" _mpi_incdirs "${_mpi_incdirs}") string(REPLACE " " ";" _mpi_libdirs "${_mpi_libdirs}") string(REPLACE " " ";" _mpi_libs "${_mpi_libs}") - set(_mpi_link_flags "") - foreach(_d ${_mpi_libdirs}) - list(APPEND _mpi_link_flags "-L${_d}") - endforeach() + set(_mpi_lib_flags "") foreach(_l ${_mpi_libs}) - list(APPEND _mpi_link_flags "-l${_l}") + list(APPEND _mpi_lib_flags "-l${_l}") endforeach() - message(STATUS " MPI includes : ${_mpi_incdirs}") - message(STATUS " MPI link : ${_mpi_link_flags}") + message(STATUS " includes : ${_mpi_incdirs}") + message(STATUS " libdirs : ${_mpi_libdirs}") + message(STATUS " libs : ${_mpi_lib_flags}") foreach(_tgt MPI::MPI_C MPI::MPI_CXX) if(NOT TARGET ${_tgt}) add_library(${_tgt} INTERFACE IMPORTED) endif() set_target_properties(${_tgt} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${_mpi_incdirs}" - INTERFACE_LINK_LIBRARIES "${_mpi_link_flags}") + INTERFACE_LINK_DIRECTORIES "${_mpi_libdirs}" + INTERFACE_LINK_LIBRARIES "${_mpi_lib_flags}") endforeach() else() find_package(MPI REQUIRED COMPONENTS C CXX) From c80f31a219c7d637bf4e90773f9e91968451f698 Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Wed, 10 Jun 2026 10:45:18 +0200 Subject: [PATCH 25/26] feat: add -h/--help to every benchmark (weak benchmark_help symbol) parse_common_args now recognises -h, --help, and -help: prints common options on rank 0, then any benchmark-specific options from a weak benchmark_help symbol, then MPI_Finalize+exit(0). Benchmarks with custom flags (pairwise_b/_nb/_bsnbr, kpartners_nb, ring_nb/_bsnbr, stencil_2d_nb) override the weak NULL with a one-line help string at file scope; the other 31 benchmarks need no change and just inherit the common block. tests/test_misc.cpp: 4 new Help.* tests cover the common-only output, benchmark-specific section, all 3 flag forms, and rank-0-only printing. README.md: -h/--help row added to the common-flags table. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 3 ++ src/common.c | 63 +++++++++++++++++++++++++++++- src/common.h | 7 ++++ src/kpartners/kpartners_nb.c | 3 ++ src/pairwise/pairwise_b.c | 8 ++++ src/pairwise/pairwise_bsnbr.c | 8 ++++ src/pairwise/pairwise_nb.c | 8 ++++ src/ring/ring_bsnbr.c | 3 ++ src/ring/ring_nb.c | 3 ++ src/stencil/stencil_2d_nb.c | 5 +++ tests/test_misc.cpp | 73 +++++++++++++++++++++++++++++++++++ 11 files changed, 183 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a9d49aef7..88be4e68e 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,9 @@ Every benchmark understands the following flags: | `-plotbins ` | `10` | Number of histogram bins | | `-plotbinsize ` | — | Fixed bin width with optional unit (e.g. `2ms`, `500us`, `0.001`); overrides `-plotbins`, linear bins only | | `-plotlog` | off | Use logarithmic (geometric) bins — good for heavy-tailed latencies | +| `-h`, `--help`, `-help` | — | Print usage (common flags + benchmark-specific options if any) and exit | + +> **Tip:** every benchmark accepts `-h` (or `--help`); benchmarks with extra flags (`pairwise_*`, `kpartners_nb`, `ring_*`, `stencil_2d_nb`) append a `Benchmark-specific options` section listing them. ### Burst distributions diff --git a/src/common.c b/src/common.c index c3c8d952b..1c350ca36 100644 --- a/src/common.c +++ b/src/common.c @@ -51,6 +51,11 @@ double burst_shape = 1.5; char pause_dist[16] = "exp"; double pause_shape = 1.5; +/* Weak default — benchmarks that take their own flags override this with a + * strong definition at file scope (a one-liner `const char *benchmark_help = + * "..."` in the .c file). Benchmarks with no custom flags need do nothing. */ +__attribute__((weak)) const char *benchmark_help = NULL; + #ifndef BLINK_PLOT_MAX_BINS #define BLINK_PLOT_MAX_BINS 64 #endif @@ -172,6 +177,55 @@ void validate_burst_dist(const char *what, const char *dist, } } +/* Print the help block: common options + benchmark_help (if the benchmark + * provides one). Only the master rank prints, to avoid w_size duplicated + * copies on stdout. Callers handle MPI_Finalize + exit afterwards. */ +void print_help(const char *progname) +{ + if (my_rank != 0) return; + const char *base = progname ? progname : "benchmark"; + const char *slash = strrchr(base, '/'); + if (slash) base = slash + 1; + fprintf(stdout, +"Usage: %s [options]\n" +"\n" +"Common options:\n" +" -mrank master / root rank (default 0)\n" +" -mrand choose master rank at random (uses -seed)\n" +" -msgsize message size in bytes (default 1024)\n" +" -iter number of measured iterations (default 1)\n" +" -warmup number of warm-up iterations (default 5)\n" +" -endl run endlessly until SIGUSR1\n" +" -seed RNG seed shared across ranks (default 1)\n" +" -grty ops per timed window (granularity, default 1)\n" +" -maxsamples cap on per-rank samples stored (default 1000)\n" +" -debug print per-rank DEBUG lines instead of timings\n" +" -pretty-print human-readable output (default: CSV row)\n" +"\n" +"Burst / pause (idle gaps between bursts of communication):\n" +" -blength burst length (default 0 = no bursting)\n" +" -bpause pause length between bursts (default 0)\n" +" -bldist randomise burst length (default: fixed)\n" +" -bpdist randomise pause length (default: fixed)\n" +" -blshape shape param for burst dist (pareto alpha / lognormal sigma)\n" +" -bpshape shape param for pause dist\n" +"\n" +"Per-iteration latency histogram (rank 0 only, end of run):\n" +" -plot emit ASCII histogram of per-iteration latencies\n" +" -plotstat cross-rank statistic (default: max)\n" +" -plotbins number of bins (default 10, max 64)\n" +" -plotbinsize bin width (e.g. 2ms, 500us, 0.001) — overrides -plotbins\n" +" -plotlog logarithmic bar heights\n" +"\n" +" -h, --help show this help and exit\n", + base); + + if (benchmark_help && *benchmark_help) { + fprintf(stdout, "\nBenchmark-specific options:\n%s", benchmark_help); + } + fflush(stdout); +} + /* Parse a duration: a number with an optional unit suffix (s, ms, us, ns). * A bare number is interpreted as seconds (consistent with -blength/-bpause). * Returns the value in seconds, or NAN on a malformed string / unknown unit. */ @@ -202,7 +256,14 @@ int parse_common_args(int argc, char **argv) int i; for (i = 1; i < argc; i++) { - if (strcmp(argv[i], "-mrank") == 0) { master_rank = atoi(arg_value(argc, argv, &i)); } + if (strcmp(argv[i], "-h") == 0 + || strcmp(argv[i], "-help") == 0 + || strcmp(argv[i], "--help") == 0) { + print_help(argv[0]); + MPI_Finalize(); + exit(0); + } + else if (strcmp(argv[i], "-mrank") == 0) { master_rank = atoi(arg_value(argc, argv, &i)); } else if (strcmp(argv[i], "-mrand") == 0) { do_rand_master = 1; } else if (strcmp(argv[i], "-msgsize") == 0) { msg_size = atoi(arg_value(argc, argv, &i)); } else if (strcmp(argv[i], "-endl") == 0) { endless = 1; } diff --git a/src/common.h b/src/common.h index 77db72048..b039de690 100644 --- a/src/common.h +++ b/src/common.h @@ -56,6 +56,12 @@ extern double burst_shape; extern char pause_dist[16]; extern double pause_shape; +/* Benchmark-specific help text, printed after the common block by -h/--help. + * Weakly defined as NULL in common.c; benchmarks that take their own flags + * provide a strong definition at file scope (see e.g. src/pairwise/pairwise_b.c). + */ +extern const char *benchmark_help; + /* ── functions (defined in common.c) ─────────────────────────────────────── */ double rand_exp(double mean, double shape); double rand_pareto(double mean, double shape); @@ -66,6 +72,7 @@ int compare_doubles(const void *p1, const void *p2); const char *arg_value(int argc, char **argv, int *i); void validate_burst_dist(const char *what, const char *dist, double mean, double shape); double parse_duration(const char *s); +void print_help(const char *progname); int parse_common_args(int argc, char **argv); double sample_burst_length(double mean); double sample_pause_length(double mean); diff --git a/src/kpartners/kpartners_nb.c b/src/kpartners/kpartners_nb.c index ad40eebfc..dd8dc4996 100644 --- a/src/kpartners/kpartners_nb.c +++ b/src/kpartners/kpartners_nb.c @@ -10,6 +10,9 @@ #include #include "common.h" +const char *benchmark_help = +" -k number of random partners per rank (default 1, capped at w_size-1)\n"; + int main(int argc, char** argv){ /*init MPI world*/ diff --git a/src/pairwise/pairwise_b.c b/src/pairwise/pairwise_b.c index adcef26b5..0946a7348 100644 --- a/src/pairwise/pairwise_b.c +++ b/src/pairwise/pairwise_b.c @@ -10,6 +10,14 @@ #include #include "common.h" +const char *benchmark_help = +" -mode pairing strategy (default: offpair)\n" +" offpair symmetric offset pairs (uses -offset)\n" +" rpair random reciprocal pairing (uses -seed)\n" +" rot rotation: rank r -> (r+offset) mod w_size; non-reciprocal\n" +" perm random permutation; non-reciprocal\n" +" -offset offset for offpair/rot modes (default 1)\n"; + int main(int argc, char** argv){ /*init MPI world*/ diff --git a/src/pairwise/pairwise_bsnbr.c b/src/pairwise/pairwise_bsnbr.c index 9990ac884..b7e7a356b 100644 --- a/src/pairwise/pairwise_bsnbr.c +++ b/src/pairwise/pairwise_bsnbr.c @@ -10,6 +10,14 @@ #include #include "common.h" +const char *benchmark_help = +" -mode pairing strategy (default: offpair)\n" +" offpair symmetric offset pairs (uses -offset)\n" +" rpair random reciprocal pairing (uses -seed)\n" +" rot rotation: rank r -> (r+offset) mod w_size; non-reciprocal\n" +" perm random permutation; non-reciprocal\n" +" -offset offset for offpair/rot modes (default 1)\n"; + int main(int argc, char** argv){ /*init MPI world*/ diff --git a/src/pairwise/pairwise_nb.c b/src/pairwise/pairwise_nb.c index a3a67add4..ea6ed9c9c 100644 --- a/src/pairwise/pairwise_nb.c +++ b/src/pairwise/pairwise_nb.c @@ -10,6 +10,14 @@ #include #include "common.h" +const char *benchmark_help = +" -mode pairing strategy (default: offpair)\n" +" offpair symmetric offset pairs (uses -offset)\n" +" rpair random reciprocal pairing (uses -seed)\n" +" rot rotation: rank r -> (r+offset) mod w_size; non-reciprocal\n" +" perm random permutation; non-reciprocal\n" +" -offset offset for offpair/rot modes (default 1)\n"; + int main(int argc, char** argv){ /*init MPI world*/ diff --git a/src/ring/ring_bsnbr.c b/src/ring/ring_bsnbr.c index 9af883fcd..ffb55abd6 100644 --- a/src/ring/ring_bsnbr.c +++ b/src/ring/ring_bsnbr.c @@ -10,6 +10,9 @@ #include #include "common.h" +const char *benchmark_help = +" -rring randomise the ring topology (default: sequential 0->1->...->N-1)\n"; + int main(int argc, char** argv){ /*init MPI world*/ diff --git a/src/ring/ring_nb.c b/src/ring/ring_nb.c index 95ecda326..06dea039a 100644 --- a/src/ring/ring_nb.c +++ b/src/ring/ring_nb.c @@ -10,6 +10,9 @@ #include #include "common.h" +const char *benchmark_help = +" -rring randomise the ring topology (default: sequential 0->1->...->N-1)\n"; + int main(int argc, char** argv){ /*init MPI world*/ diff --git a/src/stencil/stencil_2d_nb.c b/src/stencil/stencil_2d_nb.c index 7eecd454d..055450c15 100644 --- a/src/stencil/stencil_2d_nb.c +++ b/src/stencil/stencil_2d_nb.c @@ -10,6 +10,11 @@ #include #include "common.h" +const char *benchmark_help = +" -dimx first dim of the 2D Cartesian grid (default 0 = auto via MPI_Dims_create)\n" +" if non-zero, must divide w_size; w_size/dimx becomes the Y dim\n" +" -periodic enable wrap-around (toroidal) boundaries on both dims\n"; + int main(int argc, char** argv){ /*init MPI world*/ diff --git a/tests/test_misc.cpp b/tests/test_misc.cpp index 3b383c182..c66feea10 100644 --- a/tests/test_misc.cpp +++ b/tests/test_misc.cpp @@ -317,6 +317,79 @@ TEST(BurstSampling, InvalidShapeAbortsCleanly) << "expected a pareto-shape diagnostic.\nstderr:\n" << r.stderr_raw; } +/* ── -h / --help / -help ──────────────────────────────────────────────────── + * + * The help system: weak `benchmark_help` symbol in common.c overridden by + * benchmarks with custom flags (pairwise, kpartners, ring, stencil). + * parse_common_args handles -h/-help/--help by printing on rank 0 and calling + * MPI_Finalize + exit(0) on every rank. + */ + +TEST(Help, CommonHelpPrintsAndExitsZero) +{ + /* barrier_nb has NO custom args — only the common block should appear */ + DebugRun r = run_debug_capture("barrier_nb", 2, "-h"); + ASSERT_TRUE(r.normal_exit) << "stderr:\n" << r.stderr_raw; + EXPECT_EQ(r.exit_code, 0) << "stderr:\n" << r.stderr_raw; + EXPECT_NE(r.stdout_raw.find("Usage: barrier_nb"), std::string::npos) + << "expected 'Usage: barrier_nb'.\nstdout:\n" << r.stdout_raw; + EXPECT_NE(r.stdout_raw.find("-mrank"), std::string::npos) + << "common help should list -mrank.\nstdout:\n" << r.stdout_raw; + EXPECT_NE(r.stdout_raw.find("-blength"), std::string::npos) + << "common help should list burst flags.\nstdout:\n" << r.stdout_raw; + EXPECT_NE(r.stdout_raw.find("-plot"), std::string::npos) + << "common help should list plot flags.\nstdout:\n" << r.stdout_raw; + /* benchmarks without custom args must NOT show the benchmark-specific section */ + EXPECT_EQ(r.stdout_raw.find("Benchmark-specific options"), std::string::npos) + << "barrier_nb has no custom args — the section should be absent.\nstdout:\n" + << r.stdout_raw; +} + +TEST(Help, BenchmarkSpecificSectionAppears) +{ + /* pairwise_b defines benchmark_help with -mode and -offset */ + DebugRun r = run_debug_capture("pairwise_b", 8, "--help"); + ASSERT_TRUE(r.normal_exit) << "stderr:\n" << r.stderr_raw; + EXPECT_EQ(r.exit_code, 0) << "stderr:\n" << r.stderr_raw; + EXPECT_NE(r.stdout_raw.find("Benchmark-specific options"), std::string::npos) + << "pairwise_b --help should include the benchmark-specific section.\nstdout:\n" + << r.stdout_raw; + EXPECT_NE(r.stdout_raw.find("-mode"), std::string::npos) + << "pairwise_b --help should mention -mode.\nstdout:\n" << r.stdout_raw; + EXPECT_NE(r.stdout_raw.find("-offset"), std::string::npos) + << "pairwise_b --help should mention -offset.\nstdout:\n" << r.stdout_raw; +} + +TEST(Help, AllThreeFlagFormsAccepted) +{ + for (const char *flag : {"-h", "-help", "--help"}) { + DebugRun r = run_debug_capture("barrier_nb", 2, flag); + EXPECT_TRUE(r.normal_exit && r.exit_code == 0) + << "flag '" << flag << "' should print help and exit 0.\nstderr:\n" + << r.stderr_raw; + EXPECT_NE(r.stdout_raw.find("Usage:"), std::string::npos) + << "flag '" << flag << "' — expected 'Usage:' in stdout.\nstdout:\n" + << r.stdout_raw; + } +} + +TEST(Help, OnlyMasterRankPrints) +{ + /* With -np 4, help must appear exactly once, not 4×. Catches a regression + * where print_help would skip its rank-0 guard. */ + DebugRun r = run_debug_capture("barrier_nb", 4, "-h"); + EXPECT_EQ(r.exit_code, 0) << "stderr:\n" << r.stderr_raw; + int usage_count = 0; + size_t p = 0; + while ((p = r.stdout_raw.find("Usage:", p)) != std::string::npos) { + usage_count++; + p += 6; + } + EXPECT_EQ(usage_count, 1) + << "help should print once (rank 0 only), got " << usage_count + << " copies.\nstdout:\n" << r.stdout_raw; +} + /* ── auxiliary tools (checker, null_dummy) ──────────────────────────────────── */ TEST(Tools, CheckerRuns) From 2fa08acf5fd36f3dec51627618a0b67fe6b48cec Mon Sep 17 00:00:00 2001 From: Daniele De Sensi Date: Wed, 10 Jun 2026 11:01:47 +0200 Subject: [PATCH 26/26] fix: -blength/-bpause accept unit suffixes (s, ms, us, ns) Previously parsed with atof(), which silently truncated unit suffixes: "-blength 1ms" became 1.0 (= 1 SECOND) instead of 1 millisecond. Anyone who tried to use a suffix got a result 1000-1000000x off the intent without any warning. parse_common_args now routes both flags through a new parse_duration_arg() wrapper that uses parse_duration() (same parser as -plotbinsize, already suffix-aware) and aborts with a clear diagnostic on NAN or negative input. Bare numbers continue to mean seconds, so existing scripts are unaffected. Help text and README now document the available suffixes; README example switched to "10ms" / "50ms" for readability. tests/test_misc.cpp: - DurationSuffixesAccepted exercises the suffix path. - InvalidDurationAbortsCleanly regression-guards the new validation. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 8 ++++---- src/common.c | 25 +++++++++++++++++++++---- tests/test_misc.cpp | 23 +++++++++++++++++++++++ 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 88be4e68e..0f3b610c5 100644 --- a/README.md +++ b/README.md @@ -116,10 +116,10 @@ Every benchmark understands the following flags: | `-mrank ` | `0` | Rank that collects and prints results | | `-mrand` | off | Pick master rank randomly | | `-seed ` | `1` | RNG seed (shared across ranks) | -| `-blength ` | `0` | Mean burst length in seconds (0 = single shot per iteration) | +| `-blength ` | `0` | Mean burst length (0 = single shot per iteration). Bare number = seconds; suffixes `s`, `ms`, `us`, `ns` accepted (e.g. `400us`). | | `-bldist ` | — | Randomise burst length using distribution `D` (`exp`, `pareto`, `lognormal`) | | `-blshape ` | `1.5` | Shape parameter for burst distribution (α for Pareto, σ for log-normal) | -| `-bpause ` | `0` | Mean pause between bursts in seconds | +| `-bpause ` | `0` | Mean pause between bursts. Same unit conventions as `-blength`. | | `-bpdist ` | — | Randomise pause length using distribution `D` (`exp`, `pareto`, `lognormal`) | | `-bpshape ` | `1.5` | Shape parameter for pause distribution (α for Pareto, σ for log-normal) | | `-pretty-print` | off | Human-readable table output instead of CSV (see [Output format](#output-format)) | @@ -145,8 +145,8 @@ When `-bldist` or `-bpdist` is supplied, burst lengths / pauses are drawn indepe ```bash # Pareto bursts (α=2, heavy tail) with exponential pauses mpirun -n 8 build/bin/alltoall_nb -iter 500 \ - -blength 0.01 -bldist pareto -blshape 2.0 \ - -bpause 0.05 -bpdist exp + -blength 10ms -bldist pareto -blshape 2.0 \ + -bpause 50ms -bpdist exp ``` The sampler implementations can be verified independently: diff --git a/src/common.c b/src/common.c index 1c350ca36..908ae4c7d 100644 --- a/src/common.c +++ b/src/common.c @@ -203,12 +203,13 @@ void print_help(const char *progname) " -pretty-print human-readable output (default: CSV row)\n" "\n" "Burst / pause (idle gaps between bursts of communication):\n" -" -blength burst length (default 0 = no bursting)\n" -" -bpause pause length between bursts (default 0)\n" +" -blength burst length (default 0 = no bursting)\n" +" -bpause pause length between bursts (default 0)\n" " -bldist randomise burst length (default: fixed)\n" " -bpdist randomise pause length (default: fixed)\n" " -blshape shape param for burst dist (pareto alpha / lognormal sigma)\n" " -bpshape shape param for pause dist\n" +" (durations: bare number = seconds; suffixes 's', 'ms', 'us', 'ns' accepted)\n" "\n" "Per-iteration latency histogram (rank 0 only, end of run):\n" " -plot emit ASCII histogram of per-iteration latencies\n" @@ -226,6 +227,22 @@ void print_help(const char *progname) fflush(stdout); } +/* Wrap parse_duration with diagnostics + validation for CLI args that accept + * a duration. A bare number is seconds; suffixes s/ms/us/ns are recognised. + * Aborts with a clear message on malformed input or negative value (0 is OK + * because -blength 0 / -bpause 0 mean "no bursting"). */ +static double parse_duration_arg(const char *flag, const char *s) +{ + double v = parse_duration(s); + if (isnan(v) || v < 0.0) { + if (my_rank == master_rank) + fprintf(stderr, "%s must be a non-negative duration " + "(e.g. 0.001, 1ms, 500us, 100ns), got '%s'\n", flag, s); + MPI_Abort(MPI_COMM_WORLD, -1); + } + return v; +} + /* Parse a duration: a number with an optional unit suffix (s, ms, us, ns). * A bare number is interpreted as seconds (consistent with -blength/-bpause). * Returns the value in seconds, or NAN on a malformed string / unknown unit. */ @@ -269,8 +286,8 @@ int parse_common_args(int argc, char **argv) else if (strcmp(argv[i], "-endl") == 0) { endless = 1; } else if (strcmp(argv[i], "-iter") == 0) { max_iters = atoi(arg_value(argc, argv, &i)); } else if (strcmp(argv[i], "-warmup") == 0) { warm_up_iters = atoi(arg_value(argc, argv, &i)); } - else if (strcmp(argv[i], "-blength") == 0) { burst_length = atof(arg_value(argc, argv, &i)); } - else if (strcmp(argv[i], "-bpause") == 0) { burst_pause = atof(arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-blength") == 0) { burst_length = parse_duration_arg("-blength", arg_value(argc, argv, &i)); } + else if (strcmp(argv[i], "-bpause") == 0) { burst_pause = parse_duration_arg("-bpause", arg_value(argc, argv, &i)); } else if (strcmp(argv[i], "-bldist") == 0) { strncpy(burst_dist, arg_value(argc, argv, &i), 15); burst_dist[15] = '\0'; burst_length_rand = 1; } diff --git a/tests/test_misc.cpp b/tests/test_misc.cpp index c66feea10..7b7c30b59 100644 --- a/tests/test_misc.cpp +++ b/tests/test_misc.cpp @@ -317,6 +317,29 @@ TEST(BurstSampling, InvalidShapeAbortsCleanly) << "expected a pareto-shape diagnostic.\nstderr:\n" << r.stderr_raw; } +/* + * Duration unit suffixes (s, ms, us, ns) — previously dropped silently by atof + * (so "-blength 1ms" became 1 SECOND, not 1 ms). parse_duration_arg() now + * recognises them; a bare number is still seconds for backwards compatibility. + */ +TEST(BurstSampling, DurationSuffixesAccepted) +{ + DebugRun r = run_debug_capture("barrier_nb", 4, + "-iter 3 -blength 400us -bpause 300us -bldist exp -bpdist exp"); + EXPECT_TRUE(r.normal_exit && r.exit_code == 0) + << "barrier_nb with unit-suffix durations failed.\nstderr:\n" << r.stderr_raw; +} + +TEST(BurstSampling, InvalidDurationAbortsCleanly) +{ + /* old atof would have silently returned 0.0; we now abort instead. */ + DebugRun r = run_debug_capture("barrier_nb", 2, "-blength garbage"); + EXPECT_FALSE(r.normal_exit && r.exit_code == 0) + << "non-numeric -blength should abort.\nstdout:\n" << r.stdout_raw; + EXPECT_NE(r.stderr_raw.find("must be a non-negative duration"), std::string::npos) + << "expected a duration diagnostic.\nstderr:\n" << r.stderr_raw; +} + /* ── -h / --help / -help ──────────────────────────────────────────────────── * * The help system: weak `benchmark_help` symbol in common.c overridden by