环境压测数据工具
-
bench_fd_verify.c - 验证stat和fcntl 验证 fd有效性 只读性能数据
-
bench_pipe.c - pipe 与 sockpair 的 读/写 吞吐与延迟 性能数据
环境压测数据工具
bench_fd_verify.c - 验证stat和fcntl 验证 fd有效性 只读性能数据
bench_pipe.c - pipe 与 sockpair 的 读/写 吞吐与延迟 性能数据
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include <fcntl.h> | |
| #include <sys/socket.h> | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #include <time.h> | |
| #include <string.h> | |
| #include <errno.h> | |
| #define ITERATIONS 1000000 // 每种 fd 的测试次数 | |
| // 高精度计时(秒) | |
| static double now(void) { | |
| struct timespec ts; | |
| clock_gettime(CLOCK_MONOTONIC, &ts); | |
| return ts.tv_sec + ts.tv_nsec * 1e-9; | |
| } | |
| // 循环调用 fcntl(F_GETFL) 并消耗返回值 | |
| static void test_fcntl(int fd, int iter) { | |
| volatile long dummy = 0; | |
| for (int i = 0; i < iter; ++i) { | |
| int flags = fcntl(fd, F_GETFL); | |
| if (flags == -1) { | |
| fprintf(stderr, "fcntl failed on fd %d: %s\n", fd, strerror(errno)); | |
| exit(EXIT_FAILURE); | |
| } | |
| dummy += flags; | |
| } | |
| (void)dummy; | |
| } | |
| // 循环调用 fstat 并消耗返回的结构体成员 | |
| static void test_fstat(int fd, int iter) { | |
| volatile long dummy = 0; | |
| for (int i = 0; i < iter; ++i) { | |
| struct stat st; | |
| if (fstat(fd, &st) == -1) { | |
| fprintf(stderr, "fstat failed on fd %d: %s\n", fd, strerror(errno)); | |
| exit(EXIT_FAILURE); | |
| } | |
| dummy += st.st_size; // 使用一个字段,避免被优化 | |
| } | |
| (void)dummy; | |
| } | |
| // 对单个 fd 进行两种测试并打印结果 | |
| static void benchmark_fd(const char *name, int fd) { | |
| printf("\n--- %s (fd=%d) ---\n", name, fd); | |
| double start = now(); | |
| test_fcntl(fd, ITERATIONS); | |
| double end = now(); | |
| double fcntl_time = end - start; | |
| start = now(); | |
| test_fstat(fd, ITERATIONS); | |
| end = now(); | |
| double fstat_time = end - start; | |
| printf("fcntl: %9.6f sec (avg %7.2f ns/call)\n", | |
| fcntl_time, (fcntl_time / ITERATIONS) * 1e9); | |
| printf("fstat: %9.6f sec (avg %7.2f ns/call)\n", | |
| fstat_time, (fstat_time / ITERATIONS) * 1e9); | |
| printf("ratio (fstat/fcntl): %.2f x\n", fstat_time / fcntl_time); | |
| } | |
| int main(void) { | |
| printf("===== Benchmark: fcntl vs fstat for fd validity check =====\n"); | |
| printf("Iterations per test: %d\n\n", ITERATIONS); | |
| // 1. 普通文件:使用临时文件 | |
| int file_fd = open("/tmp/bench_fd_test.txt", O_RDWR | O_CREAT | O_TRUNC, 0600); | |
| if (file_fd == -1) { | |
| perror("open file"); | |
| return EXIT_FAILURE; | |
| } | |
| // 写入一些数据,使 fstat 需要读取真实元数据 | |
| if (write(file_fd, "benchmark", 9) != 9) { | |
| perror("write"); | |
| } | |
| benchmark_fd("Regular file", file_fd); | |
| close(file_fd); | |
| unlink("/tmp/bench_fd_test.txt"); | |
| // 2. pipe | |
| int pipe_fds[2]; | |
| if (pipe(pipe_fds) == -1) { | |
| perror("pipe"); | |
| return EXIT_FAILURE; | |
| } | |
| benchmark_fd("Pipe (read end)", pipe_fds[0]); | |
| close(pipe_fds[0]); | |
| close(pipe_fds[1]); | |
| // 3. socketpair | |
| int sp_fds[2]; | |
| if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp_fds) == -1) { | |
| perror("socketpair"); | |
| return EXIT_FAILURE; | |
| } | |
| benchmark_fd("Socketpair", sp_fds[0]); | |
| close(sp_fds[0]); | |
| close(sp_fds[1]); | |
| // 4. 普通 socket (UDP, 不需要 bind 即可使用) | |
| int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); | |
| if (sock_fd == -1) { | |
| perror("socket"); | |
| return EXIT_FAILURE; | |
| } | |
| benchmark_fd("UDP socket", sock_fd); | |
| close(sock_fd); | |
| return 0; | |
| } |
| /* | |
| * bench_pipe.c — socketpair vs pipe 读写性能基准测试 | |
| * | |
| * 编译: cc -O2 -o bench_pipe bench_pipe.c -lrt | |
| * 运行: ./bench_pipe | |
| * | |
| * 测试项: | |
| * 1. 延迟测试 — 逐次 write→read,测量单次往返耗时 (round-trip) | |
| * 2. 吞吐测试 — 持续写入大块数据,测量带宽 (MB/s) | |
| * 3. 乒乓测试 — 父子进程交替收发,模拟真实 IPC 场景 | |
| * | |
| * 对比维度: socketpair (AF_UNIX SOCK_STREAM) vs pipe | |
| */ | |
| #define _GNU_SOURCE | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <stdint.h> | |
| #include <unistd.h> | |
| #include <time.h> | |
| #include <sys/socket.h> | |
| #include <sys/wait.h> | |
| #include <sys/time.h> | |
| #include <errno.h> | |
| /* ------------------------------------------------------------------ */ | |
| /* 工具 */ | |
| /* ------------------------------------------------------------------ */ | |
| static inline uint64_t now_ns(void) { | |
| struct timespec ts; | |
| clock_gettime(CLOCK_MONOTONIC, &ts); | |
| return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; | |
| } | |
| /* 消除冷启动 / CPU 频率抖动 */ | |
| static void warmup(void) { | |
| volatile uint64_t s = 0; | |
| for (int i = 0; i < 1000000; i++) s += i; | |
| (void)s; | |
| } | |
| /* ------------------------------------------------------------------ */ | |
| /* 宏 */ | |
| /* ------------------------------------------------------------------ */ | |
| #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) | |
| #define ITER_SMALL 2000000 /* 小包迭代次数 */ | |
| #define ITER_LARGE 50000 /* 大包迭代次数 */ | |
| #define TOTAL_BYTES (512ULL * 1024 * 1024) /* 吞吐测试总数据量: 512 MiB */ | |
| /* ------------------------------------------------------------------ */ | |
| /* 1. 延迟测试 — 同进程内逐包 write/read */ | |
| /* ------------------------------------------------------------------ */ | |
| typedef struct { | |
| const char *label; | |
| int fd_r; | |
| int fd_w; | |
| } channel_t; | |
| static void latency_test_one(channel_t ch, const size_t *sizes, int n, | |
| const char *tag) { | |
| printf("\n── %s latency ──────────────────────────────\n", tag); | |
| printf(" %-12s %10s %12s\n", "pkt_size", "rounds", "avg_ns"); | |
| size_t max_sz = sizes[n - 1]; | |
| char *buf = (char *)malloc(max_sz); | |
| if (!buf) { perror("malloc"); return; } | |
| memset(buf, 'A', max_sz); | |
| for (int i = 0; i < n; i++) { | |
| size_t sz = sizes[i]; | |
| int iters = (sz <= 4096) ? ITER_SMALL : ITER_LARGE; | |
| uint64_t start = now_ns(); | |
| for (int j = 0; j < iters; j++) { | |
| if (write(ch.fd_w, buf, sz) != (ssize_t)sz) { | |
| perror("write"); goto done; | |
| } | |
| if (read(ch.fd_r, buf, sz) != (ssize_t)sz) { | |
| perror("read"); goto done; | |
| } | |
| } | |
| uint64_t elapsed = now_ns() - start; | |
| double avg_ns = (double)elapsed / (double)iters; | |
| printf(" %-12lu %10d %12.1f\n", | |
| (unsigned long)sz, iters, avg_ns); | |
| } | |
| done: | |
| free(buf); | |
| } | |
| static void latency_test(void) { | |
| size_t sizes[] = { | |
| 64, 256, 1024, 4096, // 16384, 65536 | |
| }; | |
| int n = ARRAY_SIZE(sizes); | |
| /* socketpair */ | |
| { | |
| int sv[2]; | |
| if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { | |
| perror("socketpair"); return; | |
| } | |
| /* 增大内核缓冲区 */ | |
| int snd = 256 * 1024, rcv = 256 * 1024; | |
| setsockopt(sv[0], SOL_SOCKET, SO_RCVBUF, &rcv, sizeof(rcv)); | |
| setsockopt(sv[1], SOL_SOCKET, SO_SNDBUF, &snd, sizeof(snd)); | |
| channel_t ch = { .label = "socketpair", .fd_r = sv[0], .fd_w = sv[1] }; | |
| latency_test_one(ch, sizes, n, "socketpair"); | |
| close(sv[0]); close(sv[1]); | |
| } | |
| /* pipe */ | |
| { | |
| int p[2]; | |
| if (pipe(p) < 0) { perror("pipe"); return; } | |
| #ifdef F_SETPIPE_SZ | |
| fcntl(p[0], F_SETPIPE_SZ, 256 * 1024); | |
| #endif | |
| channel_t ch = { .label = "pipe", .fd_r = p[0], .fd_w = p[1] }; | |
| latency_test_one(ch, sizes, n, "pipe"); | |
| close(p[0]); close(p[1]); | |
| } | |
| } | |
| /* ------------------------------------------------------------------ */ | |
| /* 2. 吞吐测试 — 同进程块写入/读取,统计带宽 */ | |
| /* ------------------------------------------------------------------ */ | |
| static void throughput_test_one(channel_t ch, const size_t *blk_sizes, | |
| int n, const char *tag) { | |
| printf("\n── %s throughput ───────────────────────────\n", tag); | |
| printf(" %-12s %10s %12s\n", "blk_size", "MB/s", "rounds"); | |
| size_t max_sz = blk_sizes[n - 1]; | |
| char *buf = (char *)malloc(max_sz); | |
| if (!buf) { perror("malloc"); return; } | |
| memset(buf, 'B', max_sz); | |
| for (int i = 0; i < n; i++) { | |
| size_t blk = blk_sizes[i]; | |
| size_t total = TOTAL_BYTES; | |
| size_t loops = total / blk; | |
| uint64_t start = now_ns(); | |
| for (size_t j = 0; j < loops; j++) { | |
| if (write(ch.fd_w, buf, blk) != (ssize_t)blk) { | |
| perror("write"); goto done; | |
| } | |
| if (read(ch.fd_r, buf, blk) != (ssize_t)blk) { | |
| perror("read"); goto done; | |
| } | |
| } | |
| uint64_t elapsed = now_ns() - start; | |
| double sec = (double)elapsed / 1e9; | |
| double mbps = ((double)total / sec) / (1024.0 * 1024.0); | |
| printf(" %-12lu %10.1f %12lu\n", | |
| (unsigned long)blk, mbps, (unsigned long)loops); | |
| } | |
| done: | |
| free(buf); | |
| } | |
| static void throughput_test(void) { | |
| size_t blk_sizes[] = { | |
| 1024, 4096, // 16384, 65536, 262144, 1048576 | |
| }; | |
| int n = ARRAY_SIZE(blk_sizes); | |
| /* socketpair */ | |
| { | |
| int sv[2]; | |
| socketpair(AF_UNIX, SOCK_STREAM, 0, sv); | |
| int snd = 512 * 1024, rcv = 512 * 1024; | |
| setsockopt(sv[0], SOL_SOCKET, SO_RCVBUF, &rcv, sizeof(rcv)); | |
| setsockopt(sv[1], SOL_SOCKET, SO_SNDBUF, &snd, sizeof(snd)); | |
| channel_t ch = { .label = "sp", .fd_r = sv[0], .fd_w = sv[1] }; | |
| throughput_test_one(ch, blk_sizes, n, "socketpair"); | |
| close(sv[0]); close(sv[1]); | |
| } | |
| /* pipe */ | |
| { | |
| int p[2]; | |
| pipe(p); | |
| #ifdef F_SETPIPE_SZ | |
| fcntl(p[0], F_SETPIPE_SZ, 512 * 1024); | |
| #endif | |
| channel_t ch = { .label = "pp", .fd_r = p[0], .fd_w = p[1] }; | |
| throughput_test_one(ch, blk_sizes, n, "pipe"); | |
| close(p[0]); close(p[1]); | |
| } | |
| } | |
| /* ------------------------------------------------------------------ */ | |
| /* 3. 乒乓测试 — fork 子进程,父子交替收发 */ | |
| /* */ | |
| /* socketpair: 天然双向,父用 sv[0] 收发,子用 sv[1] 收发 */ | |
| /* pipe: 单向,需要两根管道 (up / down) 模拟双向 */ | |
| /* ------------------------------------------------------------------ */ | |
| static void pingpong_sp(const size_t *sizes, int n) { | |
| printf("\n── socketpair ping-pong ────────────────────\n"); | |
| printf(" %-12s %10s %12s\n", "pkt_size", "rounds", "avg_ns"); | |
| size_t max_sz = sizes[n - 1]; | |
| char *buf = (char *)malloc(max_sz); | |
| if (!buf) { perror("malloc"); return; } | |
| memset(buf, 'C', max_sz); | |
| for (int i = 0; i < n; i++) { | |
| size_t sz = sizes[i]; | |
| int iters = (sz <= 4096) ? 100000 : 20000; | |
| int sv[2]; | |
| if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { | |
| perror("socketpair"); goto done; | |
| } | |
| int s = 512 * 1024; | |
| setsockopt(sv[0], SOL_SOCKET, SO_SNDBUF, &s, sizeof(s)); | |
| setsockopt(sv[0], SOL_SOCKET, SO_RCVBUF, &s, sizeof(s)); | |
| setsockopt(sv[1], SOL_SOCKET, SO_SNDBUF, &s, sizeof(s)); | |
| setsockopt(sv[1], SOL_SOCKET, SO_RCVBUF, &s, sizeof(s)); | |
| pid_t pid = fork(); | |
| if (pid < 0) { perror("fork"); close(sv[0]); close(sv[1]); goto done; } | |
| if (pid == 0) { | |
| close(sv[0]); | |
| /* 子进程: sv[1] 先读后写 */ | |
| for (int j = 0; j < iters; j++) { | |
| if (read(sv[1], buf, sz) != (ssize_t)sz) _exit(1); | |
| if (write(sv[1], buf, sz) != (ssize_t)sz) _exit(1); | |
| } | |
| close(sv[1]); | |
| _exit(0); | |
| } | |
| close(sv[1]); | |
| /* 父进程: sv[0] 先写后读,计时 */ | |
| uint64_t start = now_ns(); | |
| for (int j = 0; j < iters; j++) { | |
| if (write(sv[0], buf, sz) != (ssize_t)sz) { perror("write"); break; } | |
| if (read(sv[0], buf, sz) != (ssize_t)sz) { perror("read"); break; } | |
| } | |
| uint64_t elapsed = now_ns() - start; | |
| close(sv[0]); | |
| waitpid(pid, NULL, 0); | |
| double avg_ns = (double)elapsed / (double)iters; | |
| printf(" %-12lu %10d %12.1f\n", (unsigned long)sz, iters, avg_ns); | |
| } | |
| done: | |
| free(buf); | |
| } | |
| static void pingpong_pp(const size_t *sizes, int n) { | |
| printf("\n── pipe ping-pong (2 pipes) ────────────────\n"); | |
| printf(" %-12s %10s %12s\n", "pkt_size", "rounds", "avg_ns"); | |
| size_t max_sz = sizes[n - 1]; | |
| char *buf = (char *)malloc(max_sz); | |
| if (!buf) { perror("malloc"); return; } | |
| memset(buf, 'C', max_sz); | |
| for (int i = 0; i < n; i++) { | |
| size_t sz = sizes[i]; | |
| int iters = (sz <= 4096) ? 100000 : 20000; | |
| int up[2], down[2]; /* up: P→C, down: C→P */ | |
| if (pipe(up) < 0 || pipe(down) < 0) { | |
| perror("pipe"); goto done; | |
| } | |
| #ifdef F_SETPIPE_SZ | |
| fcntl(up[0], F_SETPIPE_SZ, 512 * 1024); | |
| fcntl(down[0], F_SETPIPE_SZ, 512 * 1024); | |
| #endif | |
| pid_t pid = fork(); | |
| if (pid < 0) { perror("fork"); close(up[0]); close(up[1]); close(down[0]); close(down[1]); goto done; } | |
| if (pid == 0) { | |
| close(up[1]); /* 子只读 up */ | |
| close(down[0]); /* 子只写 down */ | |
| for (int j = 0; j < iters; j++) { | |
| if (read(up[0], buf, sz) != (ssize_t)sz) _exit(1); | |
| if (write(down[1], buf, sz) != (ssize_t)sz) _exit(1); | |
| } | |
| close(up[0]); | |
| close(down[1]); | |
| _exit(0); | |
| } | |
| close(up[0]); /* 父只写 up */ | |
| close(down[1]); /* 父只读 down */ | |
| uint64_t start = now_ns(); | |
| for (int j = 0; j < iters; j++) { | |
| if (write(up[1], buf, sz) != (ssize_t)sz) { perror("write"); break; } | |
| if (read(down[0], buf, sz) != (ssize_t)sz) { perror("read"); break; } | |
| } | |
| uint64_t elapsed = now_ns() - start; | |
| close(up[1]); | |
| close(down[0]); | |
| waitpid(pid, NULL, 0); | |
| double avg_ns = (double)elapsed / (double)iters; | |
| printf(" %-12lu %10d %12.1f\n", (unsigned long)sz, iters, avg_ns); | |
| } | |
| done: | |
| free(buf); | |
| } | |
| static void pingpong_test(void) { | |
| size_t sizes[] = { | |
| 64, 256, 1024, 4096, 16384, 65536 | |
| }; | |
| int n = ARRAY_SIZE(sizes); | |
| pingpong_sp(sizes, n); | |
| pingpong_pp(sizes, n); | |
| } | |
| /* ------------------------------------------------------------------ */ | |
| /* 入口 */ | |
| /* ------------------------------------------------------------------ */ | |
| int main(void) { | |
| printf("╔══════════════════════════════════════════╗\n"); | |
| printf("║ socketpair vs pipe 性能对比测试 ║\n"); | |
| printf("╚══════════════════════════════════════════╝\n"); | |
| warmup(); | |
| latency_test(); | |
| throughput_test(); | |
| pingpong_test(); | |
| printf("\nDone.\n"); | |
| return 0; | |
| } |