591 lines
21 KiB
C
591 lines
21 KiB
C
/*
|
|||
|
|
* bench.c — PackFS vs. the host filesystem, across as many operations as
|
||
|
|
* the public API exposes.
|
||
|
|
*
|
||
|
|
* This is a benchmark, not a test: it makes no pass/fail assertions, and
|
||
|
|
* its numbers are specific to whatever machine and filesystem it runs
|
||
|
|
* on — see the environment banner it prints before any numbers, and read
|
||
|
|
* it before drawing conclusions from the results. In particular:
|
||
|
|
*
|
||
|
|
* - "raw fs (no fsync)" writes go through plain POSIX open/write/close
|
||
|
|
* with no fsync, so they land in the page cache, exactly like the
|
||
|
|
* `mem` backend lands in a malloc'd buffer — this is the fair,
|
||
|
|
* apples-to-apples comparison for "how much does PackFS's own
|
||
|
|
* bookkeeping cost, independent of durability."
|
||
|
|
* - "raw fs (fsync)" adds an fsync per file, which is what it actually
|
||
|
|
* costs to make a write durable on this host — this is the fair
|
||
|
|
* comparison against nothing in PackFS, since no backend here
|
||
|
|
* fsyncs a `mem`-backed write (there is nothing on disk to sync);
|
||
|
|
* the closest durable analogue is the `dir` backend, benchmarked
|
||
|
|
* separately, and even that does not fsync per write (Section 5.4
|
||
|
|
* does not require it — only compaction and the journal do,
|
||
|
|
* Section 4.3).
|
||
|
|
* - The `dir` backend's numbers include the openat2/O_NOFOLLOW
|
||
|
|
* containment cost (Section 6.2) on every single lookup, which raw
|
||
|
|
* fs access has no equivalent of paying — that gap *is* the
|
||
|
|
* measurement, not noise to explain away.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <dirent.h>
|
||
|
|
#include <fcntl.h>
|
||
|
|
#include <pthread.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <sys/stat.h>
|
||
|
|
#include <sys/types.h>
|
||
|
|
#include <time.h>
|
||
|
|
#include <unistd.h>
|
||
|
|
|
||
|
|
#include "packfs.h"
|
||
|
|
|
||
|
|
/* ---- tunables: this is the "extreme" in "make it extreme" ---- */
|
||
|
|
#define N_SMALL 20000 /* small files for the metadata-heavy suite */
|
||
|
|
#define SMALL_SIZE 128
|
||
|
|
#define N_DIRS 4000
|
||
|
|
#define N_CONC_THREADS 8
|
||
|
|
#define OPS_PER_THREAD 4000
|
||
|
|
#define LARGE_CHUNK (64 * 1024)
|
||
|
|
|
||
|
|
static const size_t LARGE_SIZES[] = { 1u << 20, 16u << 20, 64u << 20 };
|
||
|
|
#define N_LARGE_SIZES (sizeof(LARGE_SIZES) / sizeof(LARGE_SIZES[0]))
|
||
|
|
|
||
|
|
/* ---- timing + results table ---- */
|
||
|
|
|
||
|
|
static double now_sec(void) {
|
||
|
|
struct timespec ts;
|
||
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
|
|
return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
|
||
|
|
}
|
||
|
|
|
||
|
|
typedef struct Row {
|
||
|
|
char category[48];
|
||
|
|
char backend[24];
|
||
|
|
double seconds;
|
||
|
|
double ops_per_sec;
|
||
|
|
double mb_per_sec; /* 0 if not applicable */
|
||
|
|
} Row;
|
||
|
|
|
||
|
|
static Row g_rows[256];
|
||
|
|
static int g_row_count = 0;
|
||
|
|
|
||
|
|
static void record(const char *category, const char *backend, double seconds, double count, double bytes) {
|
||
|
|
Row *r = &g_rows[g_row_count++];
|
||
|
|
snprintf(r->category, sizeof(r->category), "%s", category);
|
||
|
|
snprintf(r->backend, sizeof(r->backend), "%s", backend);
|
||
|
|
r->seconds = seconds;
|
||
|
|
r->ops_per_sec = seconds > 0 ? count / seconds : 0;
|
||
|
|
r->mb_per_sec = (bytes > 0 && seconds > 0) ? (bytes / (1024.0 * 1024.0)) / seconds : 0;
|
||
|
|
printf(" %-28s %-10s %9.4fs %14.0f ops/s", category, backend, seconds, r->ops_per_sec);
|
||
|
|
if (r->mb_per_sec > 0) printf(" %10.1f MB/s", r->mb_per_sec);
|
||
|
|
printf("\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
static void section(const char *title) {
|
||
|
|
printf("\n== %s ==\n", title);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void fmt_name(char *buf, size_t cap, const char *prefix, int i) {
|
||
|
|
snprintf(buf, cap, "%s%06d.dat", prefix, i);
|
||
|
|
}
|
||
|
|
|
||
|
|
static char g_payload[SMALL_SIZE];
|
||
|
|
|
||
|
|
/* ---- category 1: small-file create (write from scratch) ---- */
|
||
|
|
|
||
|
|
static void bench_create_mem(Vfs *v) {
|
||
|
|
char path[64];
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_SMALL; i++) {
|
||
|
|
fmt_name(path, sizeof(path), "/f", i);
|
||
|
|
int err = 0;
|
||
|
|
VfsFile *f = vfs_open(v, path, VFS_O_WRONLY | VFS_O_CREAT, &err);
|
||
|
|
vfs_write(f, g_payload, SMALL_SIZE);
|
||
|
|
vfs_close(f);
|
||
|
|
}
|
||
|
|
record("create N small files", "mem", now_sec() - t0, N_SMALL, (double)N_SMALL * SMALL_SIZE);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void bench_create_dirbackend(Vfs *v) {
|
||
|
|
char path[64];
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_SMALL; i++) {
|
||
|
|
fmt_name(path, sizeof(path), "/f", i);
|
||
|
|
int err = 0;
|
||
|
|
VfsFile *f = vfs_open(v, path, VFS_O_WRONLY | VFS_O_CREAT, &err);
|
||
|
|
vfs_write(f, g_payload, SMALL_SIZE);
|
||
|
|
vfs_close(f);
|
||
|
|
}
|
||
|
|
record("create N small files", "dir", now_sec() - t0, N_SMALL, (double)N_SMALL * SMALL_SIZE);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void bench_create_rawfs(const char *root, int fsync_each) {
|
||
|
|
char path[256];
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_SMALL; i++) {
|
||
|
|
char name[64];
|
||
|
|
fmt_name(name, sizeof(name), "/f", i);
|
||
|
|
snprintf(path, sizeof(path), "%s%s", root, name);
|
||
|
|
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||
|
|
write(fd, g_payload, SMALL_SIZE);
|
||
|
|
if (fsync_each) fsync(fd);
|
||
|
|
close(fd);
|
||
|
|
}
|
||
|
|
record("create N small files", fsync_each ? "raw+fsync" : "raw fs", now_sec() - t0, N_SMALL, (double)N_SMALL * SMALL_SIZE);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---- category 2: small-file read ---- */
|
||
|
|
|
||
|
|
static void bench_read_mem(Vfs *v) {
|
||
|
|
char path[64], buf[SMALL_SIZE];
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_SMALL; i++) {
|
||
|
|
fmt_name(path, sizeof(path), "/f", i);
|
||
|
|
int err = 0;
|
||
|
|
VfsFile *f = vfs_open(v, path, VFS_O_RDONLY, &err);
|
||
|
|
vfs_read(f, buf, sizeof(buf));
|
||
|
|
vfs_close(f);
|
||
|
|
}
|
||
|
|
record("read N small files", "mem", now_sec() - t0, N_SMALL, (double)N_SMALL * SMALL_SIZE);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void bench_read_dirbackend(Vfs *v) {
|
||
|
|
char path[64], buf[SMALL_SIZE];
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_SMALL; i++) {
|
||
|
|
fmt_name(path, sizeof(path), "/f", i);
|
||
|
|
int err = 0;
|
||
|
|
VfsFile *f = vfs_open(v, path, VFS_O_RDONLY, &err);
|
||
|
|
vfs_read(f, buf, sizeof(buf));
|
||
|
|
vfs_close(f);
|
||
|
|
}
|
||
|
|
record("read N small files", "dir", now_sec() - t0, N_SMALL, (double)N_SMALL * SMALL_SIZE);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void bench_read_rawfs(const char *root) {
|
||
|
|
char path[256], buf[SMALL_SIZE];
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_SMALL; i++) {
|
||
|
|
char name[64];
|
||
|
|
fmt_name(name, sizeof(name), "/f", i);
|
||
|
|
snprintf(path, sizeof(path), "%s%s", root, name);
|
||
|
|
int fd = open(path, O_RDONLY);
|
||
|
|
read(fd, buf, sizeof(buf));
|
||
|
|
close(fd);
|
||
|
|
}
|
||
|
|
record("read N small files", "raw fs", now_sec() - t0, N_SMALL, (double)N_SMALL * SMALL_SIZE);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---- category 3: stat ---- */
|
||
|
|
|
||
|
|
static void bench_stat_mem(Vfs *v) {
|
||
|
|
char path[64]; VfsStat st;
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_SMALL; i++) { fmt_name(path, sizeof(path), "/f", i); vfs_stat(v, path, &st); }
|
||
|
|
record("stat N files", "mem", now_sec() - t0, N_SMALL, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void bench_stat_dirbackend(Vfs *v) {
|
||
|
|
char path[64]; VfsStat st;
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_SMALL; i++) { fmt_name(path, sizeof(path), "/f", i); vfs_stat(v, path, &st); }
|
||
|
|
record("stat N files", "dir", now_sec() - t0, N_SMALL, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void bench_stat_rawfs(const char *root) {
|
||
|
|
char path[256]; struct stat st;
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_SMALL; i++) {
|
||
|
|
char name[64]; fmt_name(name, sizeof(name), "/f", i);
|
||
|
|
snprintf(path, sizeof(path), "%s%s", root, name);
|
||
|
|
stat(path, &st);
|
||
|
|
}
|
||
|
|
record("stat N files", "raw fs", now_sec() - t0, N_SMALL, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---- category 4: readdir ---- */
|
||
|
|
|
||
|
|
static void bench_readdir_mem(Vfs *v) {
|
||
|
|
double t0 = now_sec();
|
||
|
|
VfsDir dir;
|
||
|
|
vfs_readdir(v, "/", &dir);
|
||
|
|
double dt = now_sec() - t0;
|
||
|
|
record("readdir (N entries)", "mem", dt, dir.count, 0);
|
||
|
|
vfs_dir_free(&dir);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void bench_readdir_dirbackend(Vfs *v) {
|
||
|
|
double t0 = now_sec();
|
||
|
|
VfsDir dir;
|
||
|
|
vfs_readdir(v, "/", &dir);
|
||
|
|
double dt = now_sec() - t0;
|
||
|
|
record("readdir (N entries)", "dir", dt, dir.count, 0);
|
||
|
|
vfs_dir_free(&dir);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void bench_readdir_rawfs(const char *root) {
|
||
|
|
double t0 = now_sec();
|
||
|
|
DIR *d = opendir(root);
|
||
|
|
long count = 0;
|
||
|
|
struct dirent *de;
|
||
|
|
while ((de = readdir(d)) != NULL) if (de->d_name[0] != '.') count++;
|
||
|
|
closedir(d);
|
||
|
|
double dt = now_sec() - t0;
|
||
|
|
record("readdir (N entries)", "raw fs", dt, (double)count, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---- category 5: unlink ---- */
|
||
|
|
|
||
|
|
static void bench_unlink_mem(Vfs *v) {
|
||
|
|
char path[64];
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_SMALL; i++) { fmt_name(path, sizeof(path), "/f", i); vfs_unlink(v, path); }
|
||
|
|
record("unlink N files", "mem", now_sec() - t0, N_SMALL, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void bench_unlink_dirbackend(Vfs *v) {
|
||
|
|
char path[64];
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_SMALL; i++) { fmt_name(path, sizeof(path), "/f", i); vfs_unlink(v, path); }
|
||
|
|
record("unlink N files", "dir", now_sec() - t0, N_SMALL, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void bench_unlink_rawfs(const char *root) {
|
||
|
|
char path[256];
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_SMALL; i++) {
|
||
|
|
char name[64]; fmt_name(name, sizeof(name), "/f", i);
|
||
|
|
snprintf(path, sizeof(path), "%s%s", root, name);
|
||
|
|
unlink(path);
|
||
|
|
}
|
||
|
|
record("unlink N files", "raw fs", now_sec() - t0, N_SMALL, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---- category 6: mkdir/rmdir ---- */
|
||
|
|
|
||
|
|
static void bench_mkdir_mem(Vfs *v) {
|
||
|
|
char path[64];
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_DIRS; i++) { snprintf(path, sizeof(path), "/d%06d", i); vfs_mkdir(v, path); }
|
||
|
|
double dt = now_sec() - t0;
|
||
|
|
record("mkdir N dirs", "mem", dt, N_DIRS, 0);
|
||
|
|
t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_DIRS; i++) { snprintf(path, sizeof(path), "/d%06d", i); vfs_unlink(v, path); }
|
||
|
|
record("rmdir N dirs", "mem", now_sec() - t0, N_DIRS, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void bench_mkdir_dirbackend(Vfs *v) {
|
||
|
|
char path[64];
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_DIRS; i++) { snprintf(path, sizeof(path), "/d%06d", i); vfs_mkdir(v, path); }
|
||
|
|
double dt = now_sec() - t0;
|
||
|
|
record("mkdir N dirs", "dir", dt, N_DIRS, 0);
|
||
|
|
t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_DIRS; i++) { snprintf(path, sizeof(path), "/d%06d", i); vfs_unlink(v, path); }
|
||
|
|
record("rmdir N dirs", "dir", now_sec() - t0, N_DIRS, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void bench_mkdir_rawfs(const char *root) {
|
||
|
|
char path[256];
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_DIRS; i++) { snprintf(path, sizeof(path), "%s/d%06d", root, i); mkdir(path, 0755); }
|
||
|
|
double dt = now_sec() - t0;
|
||
|
|
record("mkdir N dirs", "raw fs", dt, N_DIRS, 0);
|
||
|
|
t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_DIRS; i++) { snprintf(path, sizeof(path), "%s/d%06d", root, i); rmdir(path); }
|
||
|
|
record("rmdir N dirs", "raw fs", now_sec() - t0, N_DIRS, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---- category 7: large sequential write/read ---- */
|
||
|
|
|
||
|
|
static void bench_large_mem(Vfs *v, size_t size) {
|
||
|
|
char *chunk = malloc(LARGE_CHUNK);
|
||
|
|
memset(chunk, 0x5a, LARGE_CHUNK);
|
||
|
|
char label[32]; snprintf(label, sizeof(label), "write %zuMB", size / (1024 * 1024));
|
||
|
|
|
||
|
|
int err = 0;
|
||
|
|
double t0 = now_sec();
|
||
|
|
VfsFile *f = vfs_open(v, "/large.bin", VFS_O_WRONLY | VFS_O_CREAT | VFS_O_TRUNC, &err);
|
||
|
|
for (size_t off = 0; off < size; off += LARGE_CHUNK) vfs_write(f, chunk, LARGE_CHUNK);
|
||
|
|
vfs_close(f);
|
||
|
|
record(label, "mem", now_sec() - t0, 1, (double)size);
|
||
|
|
|
||
|
|
snprintf(label, sizeof(label), "read %zuMB", size / (1024 * 1024));
|
||
|
|
t0 = now_sec();
|
||
|
|
f = vfs_open(v, "/large.bin", VFS_O_RDONLY, &err);
|
||
|
|
while (vfs_read(f, chunk, LARGE_CHUNK) > 0) {}
|
||
|
|
vfs_close(f);
|
||
|
|
record(label, "mem", now_sec() - t0, 1, (double)size);
|
||
|
|
vfs_unlink(v, "/large.bin");
|
||
|
|
free(chunk);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void bench_large_rawfs(const char *root, size_t size, int fsync_it) {
|
||
|
|
char *chunk = malloc(LARGE_CHUNK);
|
||
|
|
memset(chunk, 0x5a, LARGE_CHUNK);
|
||
|
|
char path[256]; snprintf(path, sizeof(path), "%s/large.bin", root);
|
||
|
|
char label[32]; snprintf(label, sizeof(label), "write %zuMB", size / (1024 * 1024));
|
||
|
|
|
||
|
|
double t0 = now_sec();
|
||
|
|
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||
|
|
for (size_t off = 0; off < size; off += LARGE_CHUNK) write(fd, chunk, LARGE_CHUNK);
|
||
|
|
if (fsync_it) fsync(fd);
|
||
|
|
close(fd);
|
||
|
|
record(label, fsync_it ? "raw+fsync" : "raw fs", now_sec() - t0, 1, (double)size);
|
||
|
|
|
||
|
|
snprintf(label, sizeof(label), "read %zuMB", size / (1024 * 1024));
|
||
|
|
t0 = now_sec();
|
||
|
|
fd = open(path, O_RDONLY);
|
||
|
|
while (read(fd, chunk, LARGE_CHUNK) > 0) {}
|
||
|
|
close(fd);
|
||
|
|
record(label, fsync_it ? "raw+fsync" : "raw fs", now_sec() - t0, 1, (double)size);
|
||
|
|
unlink(path);
|
||
|
|
free(chunk);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---- category 8: pack (mmap'd, read-only) random access vs raw fs ---- */
|
||
|
|
|
||
|
|
static void bench_pack_random(const char *pack_path, int *order, Vfs *v) {
|
||
|
|
char buf[SMALL_SIZE], path[64];
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int k = 0; k < N_SMALL; k++) {
|
||
|
|
fmt_name(path, sizeof(path), "/f", order[k]);
|
||
|
|
int err = 0;
|
||
|
|
VfsFile *f = vfs_open(v, path, VFS_O_RDONLY, &err);
|
||
|
|
if (f) { vfs_read(f, buf, sizeof(buf)); vfs_close(f); }
|
||
|
|
}
|
||
|
|
record("random-read N entries", "pack", now_sec() - t0, N_SMALL, (double)N_SMALL * SMALL_SIZE);
|
||
|
|
(void)pack_path;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void bench_rawfs_random(const char *root, int *order) {
|
||
|
|
char buf[SMALL_SIZE], path[256];
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int k = 0; k < N_SMALL; k++) {
|
||
|
|
char name[64]; fmt_name(name, sizeof(name), "/f", order[k]);
|
||
|
|
snprintf(path, sizeof(path), "%s%s", root, name);
|
||
|
|
int fd = open(path, O_RDONLY);
|
||
|
|
if (fd >= 0) { read(fd, buf, sizeof(buf)); close(fd); }
|
||
|
|
}
|
||
|
|
record("random-read N entries", "raw fs", now_sec() - t0, N_SMALL, (double)N_SMALL * SMALL_SIZE);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---- category 9: concurrency ---- */
|
||
|
|
|
||
|
|
typedef struct ThreadArgs {
|
||
|
|
Vfs *v;
|
||
|
|
const char *rawroot;
|
||
|
|
int thread_id;
|
||
|
|
} ThreadArgs;
|
||
|
|
|
||
|
|
static void *conc_worker_mem(void *arg) {
|
||
|
|
ThreadArgs *ta = (ThreadArgs *)arg;
|
||
|
|
char path[64];
|
||
|
|
for (int i = 0; i < OPS_PER_THREAD; i++) {
|
||
|
|
snprintf(path, sizeof(path), "/t%d_%06d.dat", ta->thread_id, i);
|
||
|
|
int err = 0;
|
||
|
|
VfsFile *f = vfs_open(ta->v, path, VFS_O_WRONLY | VFS_O_CREAT, &err);
|
||
|
|
if (f) { vfs_write(f, g_payload, SMALL_SIZE); vfs_close(f); }
|
||
|
|
f = vfs_open(ta->v, path, VFS_O_RDONLY, &err);
|
||
|
|
if (f) { char buf[SMALL_SIZE]; vfs_read(f, buf, sizeof(buf)); vfs_close(f); }
|
||
|
|
vfs_unlink(ta->v, path);
|
||
|
|
}
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void *conc_worker_rawfs(void *arg) {
|
||
|
|
ThreadArgs *ta = (ThreadArgs *)arg;
|
||
|
|
char path[256];
|
||
|
|
for (int i = 0; i < OPS_PER_THREAD; i++) {
|
||
|
|
snprintf(path, sizeof(path), "%s/t%d_%06d.dat", ta->rawroot, ta->thread_id, i);
|
||
|
|
int fd = open(path, O_WRONLY | O_CREAT, 0644);
|
||
|
|
if (fd >= 0) { write(fd, g_payload, SMALL_SIZE); close(fd); }
|
||
|
|
fd = open(path, O_RDONLY);
|
||
|
|
if (fd >= 0) { char buf[SMALL_SIZE]; read(fd, buf, sizeof(buf)); close(fd); }
|
||
|
|
unlink(path);
|
||
|
|
}
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void bench_concurrency(Vfs *v, const char *rawroot) {
|
||
|
|
pthread_t threads[N_CONC_THREADS];
|
||
|
|
ThreadArgs args[N_CONC_THREADS];
|
||
|
|
|
||
|
|
double t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_CONC_THREADS; i++) {
|
||
|
|
args[i] = (ThreadArgs){ v, NULL, i };
|
||
|
|
pthread_create(&threads[i], NULL, conc_worker_mem, &args[i]);
|
||
|
|
}
|
||
|
|
for (int i = 0; i < N_CONC_THREADS; i++) pthread_join(threads[i], NULL);
|
||
|
|
record("concurrent create+read+unlink", "mem", now_sec() - t0, (double)N_CONC_THREADS * OPS_PER_THREAD * 3, 0);
|
||
|
|
|
||
|
|
t0 = now_sec();
|
||
|
|
for (int i = 0; i < N_CONC_THREADS; i++) {
|
||
|
|
args[i] = (ThreadArgs){ NULL, rawroot, i };
|
||
|
|
pthread_create(&threads[i], NULL, conc_worker_rawfs, &args[i]);
|
||
|
|
}
|
||
|
|
for (int i = 0; i < N_CONC_THREADS; i++) pthread_join(threads[i], NULL);
|
||
|
|
record("concurrent create+read+unlink", "raw fs", now_sec() - t0, (double)N_CONC_THREADS * OPS_PER_THREAD * 3, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---- main ---- */
|
||
|
|
|
||
|
|
int main(void) {
|
||
|
|
char rawroot[] = "/tmp/packfs_bench_raw_XXXXXX";
|
||
|
|
char dirroot[] = "/tmp/packfs_bench_dir_XXXXXX";
|
||
|
|
if (!mkdtemp(rawroot) || !mkdtemp(dirroot)) { fprintf(stderr, "mkdtemp failed\n"); return 1; }
|
||
|
|
char pack_path[256];
|
||
|
|
snprintf(pack_path, sizeof(pack_path), "/tmp/packfs_bench_%d.pack", (int)getpid());
|
||
|
|
unlink(pack_path);
|
||
|
|
memset(g_payload, 0x42, sizeof(g_payload));
|
||
|
|
|
||
|
|
printf("=== PackFS vs. host filesystem: benchmark ===\n\n");
|
||
|
|
printf("environment:\n");
|
||
|
|
printf(" raw fs test root: %s\n", rawroot);
|
||
|
|
printf(" dir backend root: %s\n", dirroot);
|
||
|
|
printf(" small files (N): %d, %d bytes each\n", N_SMALL, SMALL_SIZE);
|
||
|
|
printf(" directories (N): %d\n", N_DIRS);
|
||
|
|
printf(" concurrency: %d threads x %d ops (create+read+unlink)\n", N_CONC_THREADS, OPS_PER_THREAD);
|
||
|
|
printf(" large-file sizes: ");
|
||
|
|
for (size_t i = 0; i < N_LARGE_SIZES; i++) printf("%zuMB ", LARGE_SIZES[i] / (1024 * 1024));
|
||
|
|
printf("\n");
|
||
|
|
printf(" NOTE: see the file header for what \"raw fs\" vs \"raw+fsync\" vs\n");
|
||
|
|
printf(" \"dir\" actually measure — they are not interchangeable.\n");
|
||
|
|
|
||
|
|
Vfs *v = vfs_new();
|
||
|
|
Backend *mem = backend_mem_new();
|
||
|
|
vfs_mount(v, "/", mem);
|
||
|
|
|
||
|
|
section("1. create N small files");
|
||
|
|
bench_create_mem(v);
|
||
|
|
bench_create_rawfs(rawroot, 0);
|
||
|
|
bench_create_rawfs(rawroot, 1);
|
||
|
|
|
||
|
|
section("2. read N small files");
|
||
|
|
bench_read_mem(v);
|
||
|
|
bench_read_rawfs(rawroot);
|
||
|
|
|
||
|
|
section("3. stat N files");
|
||
|
|
bench_stat_mem(v);
|
||
|
|
bench_stat_rawfs(rawroot);
|
||
|
|
|
||
|
|
section("4. readdir");
|
||
|
|
bench_readdir_mem(v);
|
||
|
|
bench_readdir_rawfs(rawroot);
|
||
|
|
|
||
|
|
/* ---- dir backend, same operations, same file count, its own root ---- */
|
||
|
|
Vfs *vd = vfs_new();
|
||
|
|
int derr = 0;
|
||
|
|
Backend *dir = backend_dir_new(dirroot, &derr);
|
||
|
|
if (!dir) { fprintf(stderr, "backend_dir_new failed: %d\n", derr); return 1; }
|
||
|
|
vfs_mount(vd, "/", dir);
|
||
|
|
|
||
|
|
section("1b. create N small files (dir backend vs. what it wraps)");
|
||
|
|
bench_create_dirbackend(vd);
|
||
|
|
section("2b. read N small files (dir backend)");
|
||
|
|
bench_read_dirbackend(vd);
|
||
|
|
section("3b. stat N files (dir backend)");
|
||
|
|
bench_stat_dirbackend(vd);
|
||
|
|
section("4b. readdir (dir backend)");
|
||
|
|
bench_readdir_dirbackend(vd);
|
||
|
|
|
||
|
|
section("5. unlink N files");
|
||
|
|
bench_unlink_mem(v);
|
||
|
|
bench_unlink_dirbackend(vd);
|
||
|
|
bench_unlink_rawfs(rawroot);
|
||
|
|
|
||
|
|
section("6. mkdir/rmdir N directories");
|
||
|
|
bench_mkdir_mem(v);
|
||
|
|
bench_mkdir_dirbackend(vd);
|
||
|
|
bench_mkdir_rawfs(rawroot);
|
||
|
|
|
||
|
|
section("7. large sequential write/read");
|
||
|
|
for (size_t i = 0; i < N_LARGE_SIZES; i++) bench_large_mem(v, LARGE_SIZES[i]);
|
||
|
|
for (size_t i = 0; i < N_LARGE_SIZES; i++) bench_large_rawfs(rawroot, LARGE_SIZES[i], 0);
|
||
|
|
for (size_t i = 0; i < N_LARGE_SIZES; i++) bench_large_rawfs(rawroot, LARGE_SIZES[i], 1);
|
||
|
|
|
||
|
|
/* ---- pack: build one, then random-access read it ---- */
|
||
|
|
section("8. random-access read: mmap'd pack vs. raw fs");
|
||
|
|
{
|
||
|
|
/* re-populate mem with N_SMALL files, then compact into a pack */
|
||
|
|
for (int i = 0; i < N_SMALL; i++) {
|
||
|
|
char path[64]; fmt_name(path, sizeof(path), "/f", i);
|
||
|
|
int err = 0;
|
||
|
|
VfsFile *f = vfs_open(v, path, VFS_O_WRONLY | VFS_O_CREAT, &err);
|
||
|
|
vfs_write(f, g_payload, SMALL_SIZE);
|
||
|
|
vfs_close(f);
|
||
|
|
}
|
||
|
|
Backend *ovmem = backend_mem_new();
|
||
|
|
int oerr = 0;
|
||
|
|
Vfs *vc = vfs_new();
|
||
|
|
Backend *ov = backend_overlay_new(pack_path, ovmem, &oerr);
|
||
|
|
vfs_mount(vc, "/", ov);
|
||
|
|
for (int i = 0; i < N_SMALL; i++) {
|
||
|
|
char path[64]; fmt_name(path, sizeof(path), "/f", i);
|
||
|
|
int err = 0;
|
||
|
|
VfsFile *f = vfs_open(vc, path, VFS_O_WRONLY | VFS_O_CREAT, &err);
|
||
|
|
vfs_write(f, g_payload, SMALL_SIZE);
|
||
|
|
vfs_close(f);
|
||
|
|
}
|
||
|
|
double t0 = now_sec();
|
||
|
|
vfs_sync(vc, "/");
|
||
|
|
double compact_dt = now_sec() - t0;
|
||
|
|
record("compact N entries to pack", "pack", compact_dt, N_SMALL, (double)N_SMALL * SMALL_SIZE);
|
||
|
|
vfs_unmount(vc, "/");
|
||
|
|
backend_free(ov);
|
||
|
|
backend_free(ovmem);
|
||
|
|
vfs_free(vc);
|
||
|
|
|
||
|
|
/* same N files as plain files on the raw fs, for the random-read comparison */
|
||
|
|
for (int i = 0; i < N_SMALL; i++) {
|
||
|
|
char name[64], path[256];
|
||
|
|
fmt_name(name, sizeof(name), "/f", i);
|
||
|
|
snprintf(path, sizeof(path), "%s%s", rawroot, name);
|
||
|
|
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||
|
|
write(fd, g_payload, SMALL_SIZE);
|
||
|
|
close(fd);
|
||
|
|
}
|
||
|
|
|
||
|
|
int *order = malloc(sizeof(int) * N_SMALL);
|
||
|
|
srand(12345);
|
||
|
|
for (int i = 0; i < N_SMALL; i++) order[i] = i;
|
||
|
|
for (int i = N_SMALL - 1; i > 0; i--) { int j = rand() % (i + 1); int t = order[i]; order[i] = order[j]; order[j] = t; }
|
||
|
|
|
||
|
|
Vfs *vp = vfs_new();
|
||
|
|
int perr = 0;
|
||
|
|
Backend *ro = backend_pack_new(pack_path, &perr);
|
||
|
|
vfs_mount(vp, "/", ro);
|
||
|
|
bench_pack_random(pack_path, order, vp);
|
||
|
|
bench_rawfs_random(rawroot, order);
|
||
|
|
vfs_unmount(vp, "/");
|
||
|
|
backend_free(ro);
|
||
|
|
vfs_free(vp);
|
||
|
|
free(order);
|
||
|
|
}
|
||
|
|
|
||
|
|
section("9. concurrency (create+read+unlink)");
|
||
|
|
bench_concurrency(v, rawroot);
|
||
|
|
|
||
|
|
vfs_unmount(v, "/");
|
||
|
|
backend_free(mem);
|
||
|
|
vfs_free(v);
|
||
|
|
vfs_unmount(vd, "/");
|
||
|
|
backend_free(dir);
|
||
|
|
vfs_free(vd);
|
||
|
|
|
||
|
|
/* cleanup */
|
||
|
|
char cmd[1200];
|
||
|
|
snprintf(cmd, sizeof(cmd), "rm -rf '%s' '%s' '%s' '%s.jnl' '%s.tmp'", rawroot, dirroot, pack_path, pack_path, pack_path);
|
||
|
|
if (system(cmd) != 0) { /* best-effort cleanup; nothing to do if it fails */ }
|
||
|
|
|
||
|
|
printf("\n=== summary (%d measurements) ===\n", g_row_count);
|
||
|
|
printf(" %-28s %-10s %11s %16s %12s\n", "category", "backend", "seconds", "throughput", "MB/s");
|
||
|
|
for (int i = 0; i < g_row_count; i++) {
|
||
|
|
Row *r = &g_rows[i];
|
||
|
|
printf(" %-28s %-10s %10.4fs %14.0f ops/s", r->category, r->backend, r->seconds, r->ops_per_sec);
|
||
|
|
if (r->mb_per_sec > 0) printf(" %10.1f MB/s", r->mb_per_sec); else printf("%15s", "");
|
||
|
|
printf("\n");
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|