Skip to content

Instantly share code, notes, and snippets.

@CandyMi
Last active June 8, 2026 03:26
Show Gist options
  • Select an option

  • Save CandyMi/fd48fea677199114aff36e988fb957c9 to your computer and use it in GitHub Desktop.

Select an option

Save CandyMi/fd48fea677199114aff36e988fb957c9 to your computer and use it in GitHub Desktop.
Benchmark: fcntl vs fstat for fd validity check

介绍

环境压测数据工具

编译运行

  • 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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment