15#include <sys/resource.h>
16#include <sys/syscall.h>
19#define CHILD_STATUS 23
22static char self[PATH_MAX];
23static char* child_arguments[] = {self,
"--worker", NULL};
24static char* child_environment[] = {
"LC_ALL=C",
"PATH=/usr/bin:/bin", NULL};
25static volatile int probe_value;
29 unsigned iterations, samples, warmup;
30 unsigned sizes[MAX_SIZES];
37 uint64_t parent_user, parent_system, child_user, child_system;
40static int failure(
const char* operation) {
41 fprintf(stderr,
"VFORK-BENCH: FAIL operation=%s errno=%d\n", operation, errno);
45static uint64_t timeval_us(
const struct timeval* value) {
46 return (uint64_t)value->tv_sec * 1000000 + (uint64_t)value->tv_usec;
49static int usage_snapshot(
struct usage* result) {
50 struct rusage parent, children;
51 if (getrusage(RUSAGE_SELF, &parent) || getrusage(RUSAGE_CHILDREN, &children))
52 return failure(
"getrusage");
53 result->parent_user = timeval_us(&parent.ru_utime);
54 result->parent_system = timeval_us(&parent.ru_stime);
55 result->child_user = timeval_us(&children.ru_utime);
56 result->child_system = timeval_us(&children.ru_stime);
60static int monotonic_ns(uint64_t* result) {
61 struct timespec value;
63 if (syscall(SYS_clock_gettime, CLOCK_MONOTONIC, &value))
64 return failure(
"clock_gettime");
65 *result = (uint64_t)value.tv_sec * 1000000000 + (uint64_t)value.tv_nsec;
69static int reap(pid_t child,
int expected) {
73 waited = waitpid(child, &status, 0);
74 }
while (waited < 0 && errno == EINTR);
76 return failure(
"waitpid");
77 if (!WIFEXITED(status) || WEXITSTATUS(status) != expected) {
78 fprintf(stderr,
"VFORK-BENCH: FAIL operation=child-status status=%d\n", status);
84static int spawn(
int execute) {
85 pid_t child = call_fork ? fork() : vfork();
88 execve(self, child_arguments, child_environment);
94 return failure(call_fork ?
"fork" :
"vfork");
95 return reap(child, CHILD_STATUS);
98static const char* probe(
void) {
101 pid_t child = vfork();
107 failure(
"vfork-probe");
110 if (reap(child, CHILD_STATUS))
112 return probe_value ?
"shared" :
"fork";
115static int warm_executable(
void) {
116 int fd = open(self, O_RDONLY);
118 return failure(
"open-self");
122 count = read(fd, buffer,
sizeof(buffer));
123 }
while (count > 0 || (count < 0 && errno == EINTR));
124 int saved_errno = errno;
126 return failure(
"close-self");
129 return failure(
"read-self");
134static int measure(
const char* variant,
unsigned mib,
int execute,
unsigned sample,
135 unsigned iterations) {
136 struct usage before, after;
138 if (usage_snapshot(&before) || monotonic_ns(&start))
140 for (
unsigned i = 0; i < iterations; ++i)
143 if (monotonic_ns(&end) || usage_snapshot(&after))
145 if (end < start || after.parent_user < before.parent_user ||
146 after.parent_system < before.parent_system || after.child_user < before.child_user ||
147 after.child_system < before.child_system)
148 return failure(
"nonmonotonic-counter");
149 printf(
"sample,%s,%s,%s,%u,%u,%u,%" PRIu64
",%" PRIu64
",%" PRIu64
",%" PRIu64
",%" PRIu64
"\n",
150 variant, call_fork ?
"fork" :
"vfork", execute ?
"exec" :
"exit", mib, sample, iterations,
151 end - start, after.parent_user - before.parent_user,
152 after.parent_system - before.parent_system, after.child_user - before.child_user,
153 after.child_system - before.child_system);
157static int number(
const char* text,
unsigned minimum,
unsigned maximum,
unsigned* value) {
160 unsigned long parsed = strtoul(text, &end, 10);
161 if (!*text || *end || errno || parsed < minimum || parsed > maximum)
163 *value = (unsigned)parsed;
169 while (*text &&
options->size_count < MAX_SIZES) {
172 unsigned long value = strtoul(text, &end, 10);
173 if (end == text || errno || value > 1024 || (*end && *end !=
','))
183static void help(
void) {
185 "Usage: vfork-benchmark [--iterations N] [--samples N] [--warmup N]\n"
186 " [--sizes 0,16,64] [--mode both|exec|exit] [--expect shared|fork]\n"
187 " [--call vfork|fork]\n"
188 "Measures sequential process creation plus child completion. Sizes are parent\n"
189 "payload MiB added to the program's baseline memory, not measured total RSS.\n"
190 "Payload pages are touched once before warmup and remain untouched in timing.");
193int main(
int argc,
char** argv) {
194 if (argc == 2 && !strcmp(argv[1],
"--worker"))
197 for (
int i = 1; i < argc; ++i) {
198 const char* name = argv[i];
199 if (!strcmp(name,
"--help")) {
205 const char* value = argv[i];
206 if (!strcmp(name,
"--iterations")) {
207 if (number(value, 1, 10000, &
options.iterations))
209 }
else if (!strcmp(name,
"--samples")) {
210 if (number(value, 1, 100, &
options.samples))
212 }
else if (!strcmp(name,
"--warmup")) {
213 if (number(value, 0, 1000, &
options.warmup))
215 }
else if (!strcmp(name,
"--sizes")) {
218 }
else if (!strcmp(name,
"--mode")) {
219 options.mode = !strcmp(value,
"both") ? 2
220 : !strcmp(value,
"exec") ? 1
221 : !strcmp(value,
"exit") ? 0
225 }
else if (!strcmp(name,
"--expect")) {
226 if (strcmp(value,
"shared") && strcmp(value,
"fork"))
229 }
else if (!strcmp(name,
"--call")) {
230 if (strcmp(value,
"vfork") && strcmp(value,
"fork"))
232 call_fork = !strcmp(value,
"fork");
236 setvbuf(stdout, NULL, _IOLBF, 0);
237 long page_size = sysconf(_SC_PAGESIZE);
238 ssize_t path_size = readlink(
"/proc/self/exe", self,
sizeof(self) - 1);
239 if (page_size <= 0 || path_size <= 0 || path_size >= (ssize_t)
sizeof(self) - 1)
240 return failure(
"startup") != 0;
242 const char* variant = probe();
245 printf(
"VFORK-BENCH: PROBE variant=%s\n", variant);
247 fprintf(stderr,
"VFORK-BENCH: FAIL expected=%s observed=%s\n",
options.expect, variant);
250 if (warm_executable())
253 "VFORK-BENCH: BEGIN iterations=%u samples=%u warmup=%u page_bytes=%ld "
254 "clock=monotonic-syscall payload=touched-once faults=unsupported maxrss=unsupported\n",
257 "kind,variant,call,mode,payload_mib,sample,iterations,wall_ns,parent_user_us,parent_system_"
259 "children_user_us,children_system_us");
260 for (
size_t size = 0; size <
options.size_count; ++size) {
261 unsigned mib =
options.sizes[size];
262 size_t bytes = (size_t)mib * 1024 * 1024;
263 volatile unsigned char* payload = NULL;
265 payload = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
266 if (payload == MAP_FAILED)
267 return failure(
"mmap") != 0;
268 for (
size_t offset = 0; offset < bytes; offset += (size_t)page_size)
269 payload[offset] = 0x5a;
270 payload[bytes - 1] = 0xa5;
272 for (
unsigned i = 0; i <
options.warmup; ++i)
273 for (
int mode = 0; mode < 2; ++mode)
276 for (
unsigned sample = 1; sample <=
options.samples; ++sample)
277 for (
int position = 0; position < (
options.mode == 2 ? 2 : 1); ++position) {
278 int mode =
options.mode == 2 ? ((sample + position) & 1) :
options.mode;
279 if (measure(variant, mib, mode, sample,
options.iterations))
282 if (bytes && munmap((
void*)payload, bytes))
283 return failure(
"munmap") != 0;
285 puts(
"VFORK-BENCH: PASS");