Last active
August 31, 2025 06:58
-
-
Save sgn00/2314ec8055f8ddaae4981d969ec22ab8 to your computer and use it in GitHub Desktop.
Pipe RTT ping-pong benchmark
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // g++ ping_pong.cpp -o ping_pong -std=c++20 -O3 | |
| #include <algorithm> | |
| #include <cerrno> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <cstdlib> | |
| #include <iostream> | |
| #include <vector> | |
| #include <chrono> | |
| #include <fcntl.h> | |
| #include <sched.h> | |
| #include <sys/types.h> | |
| #include <sys/wait.h> | |
| #include <unistd.h> | |
| static void die(const char* what) { | |
| std::cerr << what << "\n"; | |
| std::exit(1); | |
| } | |
| static void print_help(const char* prog) { | |
| std::cout << "Usage: " << prog << " [options] [iterations] [msg_size] [parent_cpu] [child_cpu]\n\n" | |
| << "Options:\n" | |
| << " -b Use blocking pipes (default is non-blocking)\n" | |
| << " -h Show this help message and exit\n\n" | |
| << "Arguments:\n" | |
| << " iterations Number of ping-pong iterations (default: 10000)\n" | |
| << " msg_size Message size in bytes (default: 64)\n" | |
| << " parent_cpu CPU to pin parent process to (default: 0)\n" | |
| << " child_cpu CPU to pin child process to (default: 1)\n\n" | |
| << "Examples:\n" | |
| << " " << prog << " # Non-blocking, defaults (10k iters, 64B, CPU0/CPU1)\n" | |
| << " " << prog << " -b 20000 256 2 3 # Blocking mode, 20k iters, 256B, CPUs 2/3\n"; | |
| } | |
| static void pin_to_cpu(int cpu) { | |
| cpu_set_t set; | |
| CPU_ZERO(&set); | |
| CPU_SET(cpu, &set); | |
| if (sched_setaffinity(0, sizeof(set), &set) != 0) { | |
| die("sched_setaffinity failed"); | |
| } | |
| } | |
| static void set_nonblock(int fd) { | |
| int flags = fcntl(fd, F_GETFL, 0); | |
| if (flags < 0) { | |
| die("fcntl(F_GETFL) failed"); | |
| } | |
| if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { | |
| die("fcntl(F_SETFL O_NONBLOCK) failed"); | |
| } | |
| } | |
| static ssize_t write_full(int fd, const uint8_t* buf, size_t len) { | |
| size_t off = 0; | |
| while (off < len) { | |
| ssize_t n = ::write(fd, buf + off, len - off); | |
| if (n > 0) { | |
| off += static_cast<size_t>(n); | |
| } else if (n < 0) { | |
| if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) { | |
| continue; | |
| } | |
| return -1; | |
| } | |
| } | |
| return static_cast<ssize_t>(off); | |
| } | |
| static ssize_t read_full(int fd, uint8_t* buf, size_t len) { | |
| size_t off = 0; | |
| while (off < len) { | |
| ssize_t n = ::read(fd, buf + off, len - off); | |
| if (n > 0) { | |
| off += static_cast<size_t>(n); | |
| } else if (n == 0) { | |
| return 0; // EOF | |
| } else if (n < 0) { | |
| if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) { | |
| continue; | |
| } | |
| return -1; | |
| } | |
| } | |
| return static_cast<ssize_t>(off); | |
| } | |
| static inline uint64_t now_ns() { | |
| using clock = std::chrono::steady_clock; | |
| return static_cast<uint64_t>( | |
| std::chrono::duration_cast<std::chrono::nanoseconds>( | |
| clock::now().time_since_epoch() | |
| ).count() | |
| ); | |
| } | |
| int main(int argc, char** argv) { | |
| bool blocking_mode = false; | |
| int arg_index = 1; | |
| for (; arg_index < argc && argv[arg_index][0] == '-'; ++arg_index) { | |
| std::string flag = argv[arg_index]; | |
| if (flag == "-b") { | |
| blocking_mode = true; | |
| } else if (flag == "-h") { | |
| print_help(argv[0]); | |
| return 0; | |
| } else { | |
| std::cerr << "Unknown option: " << flag << "\n"; | |
| print_help(argv[0]); | |
| return 1; | |
| } | |
| } | |
| int iterations = (argc > arg_index) ? std::max(1, std::atoi(argv[arg_index])) : 10000; | |
| int msg_size = (argc > arg_index + 1) ? std::max(1, std::atoi(argv[arg_index + 1])) : 64; | |
| int parent_cpu = (argc > arg_index + 2) ? std::atoi(argv[arg_index + 2]) : 0; | |
| int child_cpu = (argc > arg_index + 3) ? std::atoi(argv[arg_index + 3]) : 1; | |
| long ncpu = sysconf(_SC_NPROCESSORS_CONF); | |
| if (ncpu > 0) { | |
| if (parent_cpu < 0 || child_cpu < 0 || parent_cpu >= ncpu || child_cpu >= ncpu) { | |
| std::cerr << "CPU ids must be in [0," << ncpu << ")\n"; | |
| return 1; | |
| } | |
| } | |
| int p2c[2]; | |
| int c2p[2]; | |
| if (pipe(p2c) != 0) { | |
| die("pipe p2c failed"); | |
| } | |
| if (pipe(c2p) != 0) { | |
| die("pipe c2p failed"); | |
| } | |
| if (!blocking_mode) { | |
| set_nonblock(p2c[0]); | |
| set_nonblock(p2c[1]); | |
| set_nonblock(c2p[0]); | |
| set_nonblock(c2p[1]); | |
| } | |
| pid_t child = fork(); | |
| if (child < 0) { | |
| die("fork failed"); | |
| } | |
| if (child == 0) { | |
| close(p2c[1]); | |
| close(c2p[0]); | |
| pin_to_cpu(child_cpu); | |
| std::vector<uint8_t> buf(static_cast<size_t>(msg_size)); | |
| for (int i = 0; i < iterations; ++i) { | |
| if (read_full(p2c[0], buf.data(), buf.size()) <= 0) { | |
| die("child read failed"); | |
| } | |
| if (write_full(c2p[1], buf.data(), buf.size()) <= 0) { | |
| die("child write failed"); | |
| } | |
| } | |
| close(p2c[0]); | |
| close(c2p[1]); | |
| std::exit(0); | |
| } | |
| close(p2c[0]); | |
| close(c2p[1]); | |
| pin_to_cpu(parent_cpu); | |
| std::vector<uint8_t> tx(static_cast<size_t>(msg_size), 0); | |
| std::vector<uint8_t> rx(static_cast<size_t>(msg_size), 0); | |
| for (int i = 0; i < msg_size; ++i) { | |
| tx[static_cast<size_t>(i)] = static_cast<uint8_t>(i); | |
| } | |
| uint64_t t0 = now_ns(); | |
| for (int i = 0; i < iterations; ++i) { | |
| if (write_full(p2c[1], tx.data(), tx.size()) <= 0) { | |
| die("parent write failed"); | |
| } | |
| if (read_full(c2p[0], rx.data(), rx.size()) <= 0) { | |
| die("parent read failed"); | |
| } | |
| } | |
| uint64_t t1 = now_ns(); | |
| double avg_ns = static_cast<double>(t1 - t0) / static_cast<double>(iterations); | |
| std::cout << "Mode: " << (blocking_mode ? "Blocking" : "Non-blocking") << "\n"; | |
| std::cout << "Iterations: " << iterations | |
| << ", Msg: " << msg_size << " bytes, Parent CPU: " << parent_cpu | |
| << ", Child CPU: " << child_cpu << "\n"; | |
| std::cout << "Average RTT: " << avg_ns << " ns (" << (avg_ns / 1000.0) << " us)\n"; | |
| close(p2c[1]); | |
| close(c2p[0]); | |
| int st = 0; | |
| waitpid(child, &st, 0); | |
| return st == 0 ? 0 : 1; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment