Project
Loading...
Searching...
No Matches
benchmark_ShmemVsMemfd.cxx
Go to the documentation of this file.
1// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
18
19#include <chrono>
20#include <cstdint>
21#include <cstdio>
22#include <cstdlib>
23#include <cstring>
24#include <numeric>
25#include <string>
26#include <vector>
27
28#include <fcntl.h>
29#include <csignal>
30#include <sys/mman.h>
31#include <sys/socket.h>
32#include <sys/stat.h>
33#include <sys/types.h>
34#include <sys/un.h>
35#include <sys/wait.h>
36#include <unistd.h>
37#ifdef __linux__
38#include <linux/memfd.h>
39#endif
40
41#include <fairmq/Channel.h>
42#include <fairmq/Message.h>
43#include <fairmq/Parts.h>
44#include <fairmq/ProgOptions.h>
45#include <fairmq/TransportFactory.h>
46
47// ---------------------------------------------------------------------------
48// Parameters
49// ---------------------------------------------------------------------------
50static constexpr int MAX_MESSAGES = 256;
51static constexpr int N_ITERATIONS = 1000;
52static constexpr size_t ALIGNMENT = 64;
53
54struct Scenario {
55 const char* name;
56 std::vector<size_t> sizes;
57};
58
59// Scenario 1: many small-to-medium messages (realistic TPC-like mix)
60static Scenario makeManySmallScenario()
61{
62 std::vector<size_t> sizes;
63 for (int i = 0; i < 50; ++i) {
64 sizes.push_back(4 * 1024);
65 }
66 for (int i = 0; i < 30; ++i) {
67 sizes.push_back(64 * 1024);
68 }
69 for (int i = 0; i < 15; ++i) {
70 sizes.push_back(256 * 1024);
71 }
72 for (int i = 0; i < 5; ++i) {
73 sizes.push_back(1024 * 1024);
74 }
75 return {"100 messages (50x4KB + 30x64KB + 15x256KB + 5x1MB)", std::move(sizes)};
76}
77
78// Scenario 2: few large messages
79static Scenario makeFewLargeScenario()
80{
81 std::vector<size_t> sizes;
82 for (int i = 0; i < 5; ++i) {
83 sizes.push_back(16 * 1024 * 1024); // 5x16MB = 80MB total
84 }
85 return {"5 messages (5x16MB)", std::move(sizes)};
86}
87
88static size_t totalPayloadSize(const std::vector<size_t>& sizes)
89{
90 return std::accumulate(sizes.begin(), sizes.end(), size_t{0});
91}
92
93static size_t alignUp(size_t v, size_t align)
94{
95 return (v + align - 1) & ~(align - 1);
96}
97
98// Fill buffer with a pattern that depends on both iteration and message index,
99// so swapped or misrouted messages are detected.
100static void fillPattern(void* buf, size_t size, uint8_t iterSeed, int msgIndex)
101{
102 auto* p = static_cast<uint8_t*>(buf);
103 uint8_t base = static_cast<uint8_t>(iterSeed ^ (msgIndex * 37));
104 for (size_t i = 0; i < size; ++i) {
105 p[i] = static_cast<uint8_t>(base + (i & 0xFF));
106 }
107}
108
109static bool verifyPattern(const void* buf, size_t size, uint8_t iterSeed, int msgIndex)
110{
111 auto* p = static_cast<const uint8_t*>(buf);
112 uint8_t base = static_cast<uint8_t>(iterSeed ^ (msgIndex * 37));
113 for (size_t i = 0; i < size; ++i) {
114 if (p[i] != static_cast<uint8_t>(base + (i & 0xFF))) {
115 return false;
116 }
117 }
118 return true;
119}
120
121// ---------------------------------------------------------------------------
122// Timing results communicated from child to parent via pipe
123// ---------------------------------------------------------------------------
125 double totalMs;
126};
127
129 double recvMs;
130 double mmapMs;
131 double verifyMs;
132 double unmapMs;
133};
134
135using Clock = std::chrono::high_resolution_clock;
136
137static double msElapsed(Clock::time_point start, Clock::time_point end)
138{
139 return std::chrono::duration<double, std::milli>(end - start).count();
140}
141
142// ---------------------------------------------------------------------------
143// Helper: create anonymous shared memory fd (portable)
144// ---------------------------------------------------------------------------
145
146static int createAnonymousShmFd(size_t size)
147{
148#ifdef __linux__
149 int fd = memfd_create("benchmark_region", MFD_CLOEXEC);
150 if (fd < 0) {
151 perror("memfd_create");
152 return -1;
153 }
154#else
155 // macOS fallback: shm_open + shm_unlink for an anonymous-like fd
156 // shm_open names must be short (max 31 chars on macOS including the leading /)
157 static int shmCounter = 0;
158 char name[32];
159 snprintf(name, sizeof(name), "/bm_%d_%d", getpid(), shmCounter++);
160 int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
161 if (fd < 0) {
162 perror("shm_open");
163 return -1;
164 }
165 shm_unlink(name); // unlink immediately so it's anonymous
166#endif
167 if (ftruncate(fd, static_cast<off_t>(size)) != 0) {
168 perror("ftruncate");
169 close(fd);
170 return -1;
171 }
172 return fd;
173}
174
175// ---------------------------------------------------------------------------
176// Approach A: FairMQ shmem push/pull
177// ---------------------------------------------------------------------------
180 double sendMs;
181 double receiveMs;
182};
183
184static ApproachAResult benchmarkFairMQShmem(const std::vector<size_t>& sizes)
185{
186 // Use a unique IPC path and session to avoid collisions
187 std::string ipcPath = "ipc:///tmp/benchmark_fairmq_" + std::to_string(getpid());
188
189 // Pipe for child to send timing back to parent
190 int timePipe[2];
191 if (pipe(timePipe) != 0) {
192 perror("pipe");
193 exit(1);
194 }
195
196 // Sync pipe: parent writes a byte after binding, child reads before connecting
197 int syncPipe[2];
198 if (pipe(syncPipe) != 0) {
199 perror("pipe");
200 exit(1);
201 }
202
203 // Ack pipe: child writes after receiving each batch, parent reads before sending next
204 int ackPipe[2];
205 if (pipe(ackPipe) != 0) {
206 perror("pipe");
207 exit(1);
208 }
209
210 pid_t pid = fork();
211 if (pid < 0) {
212 perror("fork");
213 exit(1);
214 }
215
216 if (pid == 0) {
217 // --- Child: receiver (pull) ---
218 close(timePipe[0]); // close read end
219 close(syncPipe[1]); // close write end
220 close(ackPipe[0]); // close read end of ack pipe
221
222 // Wait for parent to bind
223 char syncByte;
224 if (read(syncPipe[0], &syncByte, 1) != 1) {
225 _exit(1);
226 }
227 close(syncPipe[0]);
228
229 size_t session = static_cast<size_t>(getppid()) * 1000 + 1;
230 fair::mq::ProgOptions config;
231 config.SetProperty<std::string>("session", std::to_string(session));
232 config.SetProperty<size_t>("shm-segment-size", size_t{2} << 30); // 2 GB
233
234 auto factory = fair::mq::TransportFactory::CreateTransportFactory("shmem", "bench_recv", &config);
235 fair::mq::Channel channel("benchmark", "pull", factory);
236 channel.Connect(ipcPath);
237 channel.Validate();
238
239 double totalReceiveMs = 0.0;
240
241 for (int iter = 0; iter < N_ITERATIONS; ++iter) {
242 fair::mq::Parts parts;
243 auto t0 = Clock::now();
244 auto rc = channel.Receive(parts, 30000); // 30s timeout
245 auto t1 = Clock::now();
246
247 if (rc < 0) {
248 fprintf(stderr, "FairMQ Receive failed: %ld\n", (long)rc);
249 _exit(1);
250 }
251
252 // Verify data integrity
253 for (int i = 0; i < static_cast<int>(parts.Size()); ++i) {
254 if (!verifyPattern(parts[i].GetData(), parts[i].GetSize(),
255 static_cast<uint8_t>(iter & 0xFF), i)) {
256 fprintf(stderr, "FairMQ: data verification failed at iter=%d msg=%d\n", iter, i);
257 _exit(1);
258 }
259 }
260 totalReceiveMs += msElapsed(t0, t1);
261
262 // Ack: signal sender that we've consumed this batch
263 char ack = 'A';
264 if (write(ackPipe[1], &ack, 1) != 1) {
265 perror("write ack");
266 _exit(1);
267 }
268 }
269
270 close(ackPipe[1]);
271 TimingResult result{totalReceiveMs};
272 if (write(timePipe[1], &result, sizeof(result)) != sizeof(result)) {
273 perror("write timing");
274 }
275 close(timePipe[1]);
276 _exit(0);
277 }
278
279 // --- Parent: sender (push) ---
280 close(timePipe[1]); // close write end
281 close(syncPipe[0]); // close read end
282 close(ackPipe[1]); // close write end of ack pipe
283
284 size_t session = static_cast<size_t>(getpid()) * 1000 + 1;
285 size_t shmSegSize = size_t{2} << 30; // 2 GB
286 fair::mq::ProgOptions config;
287 config.SetProperty<std::string>("session", std::to_string(session));
288 config.SetProperty<size_t>("shm-segment-size", shmSegSize);
289
290 auto factory = fair::mq::TransportFactory::CreateTransportFactory("shmem", "bench_send", &config);
291 fair::mq::Channel channel("benchmark", "push", factory);
292 channel.Bind(ipcPath);
293 channel.Validate();
294
295 // Signal child that we've bound
296 char syncByte = 'G';
297 if (write(syncPipe[1], &syncByte, 1) != 1) {
298 perror("write sync");
299 }
300 close(syncPipe[1]);
301
302 // Give child a moment to connect
303 usleep(50000);
304
305 double totalAllocFillMs = 0.0;
306 double totalSendMs = 0.0;
307
308 for (int iter = 0; iter < N_ITERATIONS; ++iter) {
309 fair::mq::Parts parts;
310
311 auto t0 = Clock::now();
312 for (int m = 0; m < static_cast<int>(sizes.size()); ++m) {
313 auto msg = factory->CreateMessage(sizes[m]);
314 fillPattern(msg->GetData(), sizes[m], static_cast<uint8_t>(iter & 0xFF), m);
315 parts.AddPart(std::move(msg));
316 }
317 auto t1 = Clock::now();
318
319 auto rc = channel.Send(parts, 30000);
320 auto t2 = Clock::now();
321
322 if (rc < 0) {
323 fprintf(stderr, "FairMQ Send failed: %ld\n", (long)rc);
324 exit(1);
325 }
326
327 totalAllocFillMs += msElapsed(t0, t1);
328 totalSendMs += msElapsed(t1, t2);
329
330 // Wait for receiver to consume this batch before sending next
331 char ack;
332 if (read(ackPipe[0], &ack, 1) != 1) {
333 fprintf(stderr, "FairMQ: failed to read ack at iter=%d\n", iter);
334 exit(1);
335 }
336 }
337
338 close(ackPipe[0]);
339
340 // Read child timing
341 TimingResult childResult{};
342 if (read(timePipe[0], &childResult, sizeof(childResult)) != sizeof(childResult)) {
343 perror("read timing");
344 }
345 close(timePipe[0]);
346
347 int status = 0;
348 waitpid(pid, &status, 0);
349 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
350 fprintf(stderr, "FairMQ child exited abnormally\n");
351 }
352
353 // Clean up IPC file
354 std::string ipcFile = "/tmp/benchmark_fairmq_" + std::to_string(getpid());
355 unlink(ipcFile.c_str());
356
357 return ApproachAResult{
358 totalAllocFillMs / N_ITERATIONS,
359 totalSendMs / N_ITERATIONS,
360 childResult.totalMs / N_ITERATIONS};
361}
362
363// ---------------------------------------------------------------------------
364// Approach B: memfd + bump allocator + UDS fd passing
365// ---------------------------------------------------------------------------
366
367// Manifest entry describing one message within the shared region
369 uint32_t offset;
370 uint32_t size;
371};
372
373struct Manifest {
374 uint32_t count;
375 uint32_t totalSize;
376 ManifestEntry entries[MAX_MESSAGES];
377};
378
379// Send fd + manifest over UDS using SCM_RIGHTS
380static bool sendFdAndManifest(int sockFd, int shmFd, const Manifest& manifest)
381{
382 struct msghdr msg = {};
383 struct iovec iov = {};
384 iov.iov_base = const_cast<Manifest*>(&manifest);
385 iov.iov_len = sizeof(manifest);
386 msg.msg_iov = &iov;
387 msg.msg_iovlen = 1;
388
389 // Ancillary data for SCM_RIGHTS
390 union {
391 char buf[CMSG_SPACE(sizeof(int))];
392 struct cmsghdr align;
393 } cmsgBuf = {};
394
395 msg.msg_control = cmsgBuf.buf;
396 msg.msg_controllen = sizeof(cmsgBuf.buf);
397
398 struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
399 cmsg->cmsg_level = SOL_SOCKET;
400 cmsg->cmsg_type = SCM_RIGHTS;
401 cmsg->cmsg_len = CMSG_LEN(sizeof(int));
402 memcpy(CMSG_DATA(cmsg), &shmFd, sizeof(int));
403
404 ssize_t sent = sendmsg(sockFd, &msg, 0);
405 return sent >= 0;
406}
407
408// Receive fd + manifest from UDS
409static bool recvFdAndManifest(int sockFd, int& shmFd, Manifest& manifest)
410{
411 struct msghdr msg = {};
412 struct iovec iov = {};
413 iov.iov_base = &manifest;
414 iov.iov_len = sizeof(manifest);
415 msg.msg_iov = &iov;
416 msg.msg_iovlen = 1;
417
418 union {
419 char buf[CMSG_SPACE(sizeof(int))];
420 struct cmsghdr align;
421 } cmsgBuf = {};
422
423 msg.msg_control = cmsgBuf.buf;
424 msg.msg_controllen = sizeof(cmsgBuf.buf);
425
426 ssize_t received = recvmsg(sockFd, &msg, 0);
427 if (received < static_cast<ssize_t>(sizeof(manifest))) {
428 return false;
429 }
430
431 struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
432 if (cmsg && cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
433 memcpy(&shmFd, CMSG_DATA(cmsg), sizeof(int));
434 return true;
435 }
436 return false;
437}
438
440 double memfdCreateMs; // memfd_create + ftruncate
441 double senderMmapMs; // mmap on sender
442 double fillMs; // fill pattern
443 double sendMs; // sendmsg (fd + manifest)
444 double senderUnmapMs; // munmap + close on sender
445 double recvMs; // recvmsg (fd + manifest)
446 double receiverMmapMs; // mmap on receiver
447 double verifyMs; // verify pattern
448 double receiverUnmapMs; // munmap + close on receiver
449};
450
451static ApproachBResult benchmarkMemfdUDS(const std::vector<size_t>& sizes)
452{
453 std::string sockPath = "/tmp/benchmark_memfd_" + std::to_string(getpid()) + ".sock";
454 unlink(sockPath.c_str());
455
456 // Pipe for child to send timing back
457 int timePipe[2];
458 if (pipe(timePipe) != 0) {
459 perror("pipe");
460 exit(1);
461 }
462
463 // Sync pipe: parent writes after listen(), child reads before connect()
464 int syncPipe[2];
465 if (pipe(syncPipe) != 0) {
466 perror("pipe");
467 exit(1);
468 }
469
470 // Compute total bump region size (with alignment)
471 size_t regionSize = 0;
472 for (int m = 0; m < static_cast<int>(sizes.size()); ++m) {
473 regionSize += alignUp(sizes[m], ALIGNMENT);
474 }
475
476 pid_t pid = fork();
477 if (pid < 0) {
478 perror("fork");
479 exit(1);
480 }
481
482 if (pid == 0) {
483 // --- Child: receiver ---
484 close(timePipe[0]);
485 close(syncPipe[1]);
486
487 // Wait for parent to listen
488 char syncByte;
489 if (read(syncPipe[0], &syncByte, 1) != 1) {
490 _exit(1);
491 }
492 close(syncPipe[0]);
493
494 int sock = socket(AF_UNIX, SOCK_STREAM, 0);
495 if (sock < 0) {
496 perror("socket");
497 _exit(1);
498 }
499
500 struct sockaddr_un addr = {};
501 addr.sun_family = AF_UNIX;
502 strncpy(addr.sun_path, sockPath.c_str(), sizeof(addr.sun_path) - 1);
503
504 if (connect(sock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) != 0) {
505 perror("connect");
506 _exit(1);
507 }
508
509 MemfdReceiverTiming timing{};
510
511 for (int iter = 0; iter < N_ITERATIONS; ++iter) {
512 Manifest manifest{};
513 int shmFd = -1;
514
515 auto t0 = Clock::now();
516 if (!recvFdAndManifest(sock, shmFd, manifest)) {
517 fprintf(stderr, "memfd: recvFdAndManifest failed at iter=%d\n", iter);
518 _exit(1);
519 }
520 auto t1 = Clock::now();
521
522 int mmapFlags = MAP_SHARED;
523#ifdef MAP_POPULATE
524 mmapFlags |= MAP_POPULATE;
525#endif
526 void* region = mmap(nullptr, manifest.totalSize, PROT_READ, mmapFlags, shmFd, 0);
527 if (region == MAP_FAILED) {
528 perror("mmap receiver");
529 _exit(1);
530 }
531 auto t2 = Clock::now();
532
533 // Verify
534 for (uint32_t m = 0; m < manifest.count; ++m) {
535 const auto& entry = manifest.entries[m];
536 if (!verifyPattern(static_cast<const uint8_t*>(region) + entry.offset,
537 entry.size, static_cast<uint8_t>(iter & 0xFF), static_cast<int>(m))) {
538 fprintf(stderr, "memfd: data verification failed at iter=%d msg=%u\n", iter, m);
539 _exit(1);
540 }
541 }
542 auto t3 = Clock::now();
543
544 munmap(region, manifest.totalSize);
545 close(shmFd);
546 auto t4 = Clock::now();
547
548 timing.recvMs += msElapsed(t0, t1);
549 timing.mmapMs += msElapsed(t1, t2);
550 timing.verifyMs += msElapsed(t2, t3);
551 timing.unmapMs += msElapsed(t3, t4);
552 }
553
554 close(sock);
555
556 if (write(timePipe[1], &timing, sizeof(timing)) != sizeof(timing)) {
557 perror("write timing");
558 }
559 close(timePipe[1]);
560 _exit(0);
561 }
562
563 // --- Parent: sender ---
564 close(timePipe[1]);
565 close(syncPipe[0]);
566
567 int listenSock = socket(AF_UNIX, SOCK_STREAM, 0);
568 if (listenSock < 0) {
569 perror("socket");
570 exit(1);
571 }
572
573 struct sockaddr_un addr = {};
574 addr.sun_family = AF_UNIX;
575 strncpy(addr.sun_path, sockPath.c_str(), sizeof(addr.sun_path) - 1);
576
577 if (bind(listenSock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) != 0) {
578 perror("bind");
579 exit(1);
580 }
581 if (listen(listenSock, 1) != 0) {
582 perror("listen");
583 exit(1);
584 }
585
586 // Signal child that we're listening
587 char syncByte = 'G';
588 if (write(syncPipe[1], &syncByte, 1) != 1) {
589 perror("write sync");
590 }
591 close(syncPipe[1]);
592
593 int connSock = accept(listenSock, nullptr, nullptr);
594 if (connSock < 0) {
595 perror("accept");
596 exit(1);
597 }
598
599 double totalMemfdCreateMs = 0.0;
600 double totalSenderMmapMs = 0.0;
601 double totalFillMs = 0.0;
602 double totalSendMs = 0.0;
603 double totalSenderUnmapMs = 0.0;
604
605 for (int iter = 0; iter < N_ITERATIONS; ++iter) {
606 auto t0 = Clock::now();
607
608 // Create anonymous shared memory region
609 int shmFd = createAnonymousShmFd(regionSize);
610 if (shmFd < 0) {
611 exit(1);
612 }
613 auto t1 = Clock::now();
614
615 int senderMmapFlags = MAP_SHARED;
616#ifdef MAP_POPULATE
617 senderMmapFlags |= MAP_POPULATE;
618#endif
619 void* region = mmap(nullptr, regionSize, PROT_READ | PROT_WRITE, senderMmapFlags, shmFd, 0);
620 if (region == MAP_FAILED) {
621 perror("mmap sender");
622 exit(1);
623 }
624 auto t2 = Clock::now();
625
626 // Bump-allocate and fill
627 Manifest manifest{};
628 manifest.count = static_cast<uint32_t>(sizes.size());
629 manifest.totalSize = static_cast<uint32_t>(regionSize);
630 size_t offset = 0;
631 for (int m = 0; m < static_cast<int>(sizes.size()); ++m) {
632 manifest.entries[m].offset = static_cast<uint32_t>(offset);
633 manifest.entries[m].size = static_cast<uint32_t>(sizes[m]);
634 fillPattern(static_cast<uint8_t*>(region) + offset, sizes[m],
635 static_cast<uint8_t>(iter & 0xFF), m);
636 offset += alignUp(sizes[m], ALIGNMENT);
637 }
638 auto t3 = Clock::now();
639
640 // Unmap before sending — pages remain in the shm/memfd object
641 munmap(region, regionSize);
642 auto t4 = Clock::now();
643
644 // Send fd + manifest
645 if (!sendFdAndManifest(connSock, shmFd, manifest)) {
646 fprintf(stderr, "memfd: sendFdAndManifest failed at iter=%d\n", iter);
647 exit(1);
648 }
649 auto t5 = Clock::now();
650
651 close(shmFd);
652 auto t6 = Clock::now();
653
654 totalMemfdCreateMs += msElapsed(t0, t1);
655 totalSenderMmapMs += msElapsed(t1, t2);
656 totalFillMs += msElapsed(t2, t3);
657 totalSenderUnmapMs += msElapsed(t3, t4) + msElapsed(t5, t6); // munmap + close
658 totalSendMs += msElapsed(t4, t5);
659 }
660
661 close(connSock);
662 close(listenSock);
663 unlink(sockPath.c_str());
664
665 // Read child timing
666 MemfdReceiverTiming childTiming{};
667 if (read(timePipe[0], &childTiming, sizeof(childTiming)) != sizeof(childTiming)) {
668 perror("read timing");
669 }
670 close(timePipe[0]);
671
672 int status = 0;
673 waitpid(pid, &status, 0);
674 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
675 fprintf(stderr, "memfd child exited abnormally\n");
676 }
677
678 return ApproachBResult{
679 totalMemfdCreateMs / N_ITERATIONS,
680 totalSenderMmapMs / N_ITERATIONS,
681 totalFillMs / N_ITERATIONS,
682 totalSendMs / N_ITERATIONS,
683 totalSenderUnmapMs / N_ITERATIONS,
684 childTiming.recvMs / N_ITERATIONS,
685 childTiming.mmapMs / N_ITERATIONS,
686 childTiming.verifyMs / N_ITERATIONS,
687 childTiming.unmapMs / N_ITERATIONS};
688}
689
690// ---------------------------------------------------------------------------
691// Approach C: slab-based memfd with oldest-possible-TF tracking
692// ---------------------------------------------------------------------------
693// Pre-create a few large slabs (memfds). Bump-allocate TFs into them.
694// The receiver tracks the oldest possible TF and incrementally calls
695// madvise(MADV_DONTNEED) on consumed regions. The sender determines
696// slab availability from the oldest-possible-TF watermark.
697
698static constexpr int N_SLABS = 4;
699static constexpr size_t SLAB_SIZE = 128 * 1024 * 1024; // 128MB per slab
700static constexpr size_t PAGE_SIZE = 4096;
701
702// Slab manifest: which slab, where in it, and per-message layout
704 int32_t slabIndex; // which slab this TF is in
705 int32_t tfIndex; // TF iteration number (for verification)
706 uint32_t count; // number of messages
707 uint32_t baseOffset; // offset within slab where this TF starts
708 uint32_t totalSize; // total bytes used by this TF
709 ManifestEntry entries[MAX_MESSAGES];
710};
711
712// Send/recv for slab manifest (plain data, no fd passing)
713static bool sendSlabManifest(int sockFd, const SlabManifest& manifest)
714{
715 ssize_t sent = send(sockFd, &manifest, sizeof(manifest), 0);
716 return sent == sizeof(manifest);
717}
718
719static bool recvSlabManifest(int sockFd, SlabManifest& manifest)
720{
721 size_t remaining = sizeof(manifest);
722 char* buf = reinterpret_cast<char*>(&manifest);
723 while (remaining > 0) {
724 ssize_t n = recv(sockFd, buf, remaining, 0);
725 if (n <= 0) {
726 return false;
727 }
728 buf += n;
729 remaining -= n;
730 }
731 return true;
732}
733
734// Oldest-possible-TF update: receiver tells sender which TFs are consumed
736 int32_t oldestPossibleTF; // all TFs with index < this are fully consumed
737};
738
739static bool sendOldestTF(int sockFd, const OldestTFUpdate& update)
740{
741 return send(sockFd, &update, sizeof(update), 0) == sizeof(update);
742}
743
744static bool recvOldestTF(int sockFd, OldestTFUpdate& update)
745{
746 return recv(sockFd, &update, sizeof(update), MSG_WAITALL) == sizeof(update);
747}
748
749// Per-slab tracking on the sender side
751 int lastTFIndex = -1; // last TF index placed in this slab
752};
753
755 double fillMs;
758 double verifyMs;
759 double madviseMs;
760};
761
767
768static ApproachCResult benchmarkSlabMemfd(const std::vector<size_t>& sizes)
769{
770 std::string sockPath = "/tmp/benchmark_slab_" + std::to_string(getpid()) + ".sock";
771 unlink(sockPath.c_str());
772
773 // Compute per-TF size
774 size_t tfSize = 0;
775 for (size_t s : sizes) {
776 tfSize += alignUp(s, ALIGNMENT);
777 }
778
779 int timePipe[2];
780 if (pipe(timePipe) != 0) {
781 perror("pipe");
782 exit(1);
783 }
784
785 int syncPipe[2];
786 if (pipe(syncPipe) != 0) {
787 perror("pipe");
788 exit(1);
789 }
790
791 // Create slabs before fork so both processes inherit the fds
792 int slabFds[N_SLABS];
793 for (int i = 0; i < N_SLABS; ++i) {
794 slabFds[i] = createAnonymousShmFd(SLAB_SIZE);
795 if (slabFds[i] < 0) {
796 fprintf(stderr, "Failed to create slab %d\n", i);
797 exit(1);
798 }
799 }
800
801 pid_t pid = fork();
802 if (pid < 0) {
803 perror("fork");
804 exit(1);
805 }
806
807 if (pid == 0) {
808 // --- Child: receiver ---
809 close(timePipe[0]);
810 close(syncPipe[1]);
811
812 char syncByte;
813 if (read(syncPipe[0], &syncByte, 1) != 1) {
814 _exit(1);
815 }
816 close(syncPipe[0]);
817
818 int sock = socket(AF_UNIX, SOCK_STREAM, 0);
819 if (sock < 0) {
820 perror("socket");
821 _exit(1);
822 }
823
824 struct sockaddr_un addr = {};
825 addr.sun_family = AF_UNIX;
826 strncpy(addr.sun_path, sockPath.c_str(), sizeof(addr.sun_path) - 1);
827
828 if (connect(sock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) != 0) {
829 perror("connect");
830 _exit(1);
831 }
832
833 // mmap all slabs PROT_READ
834 void* slabMaps[N_SLABS];
835 for (int i = 0; i < N_SLABS; ++i) {
836 slabMaps[i] = mmap(nullptr, SLAB_SIZE, PROT_READ, MAP_SHARED, slabFds[i], 0);
837 if (slabMaps[i] == MAP_FAILED) {
838 perror("mmap slab receiver");
839 _exit(1);
840 }
841 }
842
843 SlabReceiverTiming timing{};
844
845 // Per-slab high-water mark: how far we've madvised
846 size_t slabAdvisedUpTo[N_SLABS] = {};
847
848 for (int iter = 0; iter < N_ITERATIONS; ++iter) {
849 SlabManifest manifest{};
850
851 auto t0 = Clock::now();
852 if (!recvSlabManifest(sock, manifest)) {
853 fprintf(stderr, "slab: recvSlabManifest failed at iter=%d\n", iter);
854 _exit(1);
855 }
856 auto t1 = Clock::now();
857
858 // Verify
859 auto* base = static_cast<const uint8_t*>(slabMaps[manifest.slabIndex]);
860 for (uint32_t m = 0; m < manifest.count; ++m) {
861 const auto& entry = manifest.entries[m];
862 if (!verifyPattern(base + manifest.baseOffset + entry.offset,
863 entry.size, static_cast<uint8_t>(manifest.tfIndex & 0xFF),
864 static_cast<int>(m))) {
865 fprintf(stderr, "slab: data verification failed at iter=%d msg=%u slab=%d\n",
866 iter, m, manifest.slabIndex);
867 _exit(1);
868 }
869 }
870 auto t2 = Clock::now();
871
872 // Advance oldest possible TF — this TF is consumed.
873 // madvise consumed pages in this slab up to the end of this TF.
874 double madvMs = 0.0;
875 auto tm0 = Clock::now();
876 {
877 int si = manifest.slabIndex;
878 size_t tfEnd = manifest.baseOffset + manifest.totalSize;
879
880 // Detect slab reuse: if baseOffset is before our high-water mark,
881 // the sender has recycled this slab — reset tracking.
882 if (manifest.baseOffset < slabAdvisedUpTo[si]) {
883 slabAdvisedUpTo[si] = 0;
884 }
885
886 // Page-align: only madvise complete pages
887 size_t pageAlignedStart = (slabAdvisedUpTo[si] + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
888 size_t pageAlignedEnd = tfEnd & ~(PAGE_SIZE - 1);
889 if (pageAlignedEnd > pageAlignedStart) {
890#ifdef MADV_DONTNEED
891 madvise(static_cast<uint8_t*>(slabMaps[si]) + pageAlignedStart,
892 pageAlignedEnd - pageAlignedStart, MADV_DONTNEED);
893#endif
894 }
895 slabAdvisedUpTo[si] = tfEnd;
896 }
897 auto tm1 = Clock::now();
898 madvMs = msElapsed(tm0, tm1);
899
900 // Send oldest-possible-TF update to sender
901 OldestTFUpdate update{static_cast<int32_t>(iter + 1)};
902 if (!sendOldestTF(sock, update)) {
903 perror("sendOldestTF");
904 _exit(1);
905 }
906
907 timing.recvManifestMs += msElapsed(t0, t1);
908 timing.verifyMs += msElapsed(t1, t2);
909 timing.madviseMs += madvMs;
910 }
911
912 // Clean up
913 for (int i = 0; i < N_SLABS; ++i) {
914 munmap(slabMaps[i], SLAB_SIZE);
915 close(slabFds[i]);
916 }
917 close(sock);
918
919 if (write(timePipe[1], &timing, sizeof(timing)) != sizeof(timing)) {
920 perror("write timing");
921 }
922 close(timePipe[1]);
923 _exit(0);
924 }
925
926 // --- Parent: sender ---
927 close(timePipe[1]);
928 close(syncPipe[0]);
929
930 int listenSock = socket(AF_UNIX, SOCK_STREAM, 0);
931 if (listenSock < 0) {
932 perror("socket");
933 exit(1);
934 }
935
936 struct sockaddr_un addr = {};
937 addr.sun_family = AF_UNIX;
938 strncpy(addr.sun_path, sockPath.c_str(), sizeof(addr.sun_path) - 1);
939
940 if (bind(listenSock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) != 0) {
941 perror("bind");
942 exit(1);
943 }
944 if (listen(listenSock, 1) != 0) {
945 perror("listen");
946 exit(1);
947 }
948
949 char syncByte = 'G';
950 if (write(syncPipe[1], &syncByte, 1) != 1) {
951 perror("write sync");
952 }
953 close(syncPipe[1]);
954
955 int connSock = accept(listenSock, nullptr, nullptr);
956 if (connSock < 0) {
957 perror("accept");
958 exit(1);
959 }
960
961 // mmap all slabs PROT_READ|PROT_WRITE
962 void* slabMaps[N_SLABS];
963 for (int i = 0; i < N_SLABS; ++i) {
964 int flags = MAP_SHARED;
965#ifdef MAP_POPULATE
966 flags |= MAP_POPULATE;
967#endif
968 slabMaps[i] = mmap(nullptr, SLAB_SIZE, PROT_READ | PROT_WRITE, flags, slabFds[i], 0);
969 if (slabMaps[i] == MAP_FAILED) {
970 perror("mmap slab sender");
971 exit(1);
972 }
973 }
974
975 // Per-slab: track the last TF index stored in each slab
976 SenderSlabState senderSlabs[N_SLABS] = {};
977 int32_t knownOldestTF = 0; // latest oldest-possible-TF from receiver
978
979 double totalFillMs = 0.0;
980 double totalSendManifestMs = 0.0;
981
982 int currentSlab = 0;
983 size_t slabOffset = 0;
984
985 for (int iter = 0; iter < N_ITERATIONS; ++iter) {
986 // Check if current TF fits in current slab
987 if (slabOffset + tfSize > SLAB_SIZE) {
988 // Move to next slab
989 int nextSlab = (currentSlab + 1) % N_SLABS;
990
991 // A slab is available when oldestPossibleTF > lastTFIndex in that slab,
992 // meaning all TFs that were in it have been consumed.
993 while (senderSlabs[nextSlab].lastTFIndex >= 0 &&
994 knownOldestTF <= senderSlabs[nextSlab].lastTFIndex) {
995 OldestTFUpdate update{};
996 if (!recvOldestTF(connSock, update)) {
997 fprintf(stderr, "slab: recvOldestTF failed waiting for slab %d\n", nextSlab);
998 exit(1);
999 }
1000 knownOldestTF = update.oldestPossibleTF;
1001 }
1002
1003 currentSlab = nextSlab;
1004 slabOffset = 0;
1005 }
1006
1007 auto t0 = Clock::now();
1008
1009 // Bump-allocate and fill in current slab
1010 auto* base = static_cast<uint8_t*>(slabMaps[currentSlab]);
1011 SlabManifest manifest{};
1012 manifest.slabIndex = currentSlab;
1013 manifest.tfIndex = iter;
1014 manifest.count = static_cast<uint32_t>(sizes.size());
1015 manifest.baseOffset = static_cast<uint32_t>(slabOffset);
1016 manifest.totalSize = static_cast<uint32_t>(tfSize);
1017
1018 size_t localOffset = 0;
1019 for (int m = 0; m < static_cast<int>(sizes.size()); ++m) {
1020 manifest.entries[m].offset = static_cast<uint32_t>(localOffset);
1021 manifest.entries[m].size = static_cast<uint32_t>(sizes[m]);
1022 fillPattern(base + slabOffset + localOffset, sizes[m],
1023 static_cast<uint8_t>(iter & 0xFF), m);
1024 localOffset += alignUp(sizes[m], ALIGNMENT);
1025 }
1026 auto t1 = Clock::now();
1027
1028 if (!sendSlabManifest(connSock, manifest)) {
1029 fprintf(stderr, "slab: sendSlabManifest failed at iter=%d\n", iter);
1030 exit(1);
1031 }
1032 auto t2 = Clock::now();
1033
1034 senderSlabs[currentSlab].lastTFIndex = iter;
1035 slabOffset += tfSize;
1036
1037 totalFillMs += msElapsed(t0, t1);
1038 totalSendManifestMs += msElapsed(t1, t2);
1039
1040 // Read one oldest-TF update per TF to stay in sync
1041 OldestTFUpdate update{};
1042 if (!recvOldestTF(connSock, update)) {
1043 fprintf(stderr, "slab: recvOldestTF failed at iter=%d\n", iter);
1044 exit(1);
1045 }
1046 knownOldestTF = update.oldestPossibleTF;
1047 }
1048
1049 // Clean up
1050 for (int i = 0; i < N_SLABS; ++i) {
1051 munmap(slabMaps[i], SLAB_SIZE);
1052 close(slabFds[i]);
1053 }
1054 close(connSock);
1055 close(listenSock);
1056 unlink(sockPath.c_str());
1057
1058 // Read child timing
1059 SlabReceiverTiming childTiming{};
1060 if (read(timePipe[0], &childTiming, sizeof(childTiming)) != sizeof(childTiming)) {
1061 perror("read timing");
1062 }
1063 close(timePipe[0]);
1064
1065 int status = 0;
1066 waitpid(pid, &status, 0);
1067 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
1068 fprintf(stderr, "slab child exited abnormally\n");
1069 }
1070
1071 return ApproachCResult{
1072 totalFillMs / N_ITERATIONS,
1073 totalSendManifestMs / N_ITERATIONS,
1074 childTiming.recvManifestMs / N_ITERATIONS,
1075 childTiming.verifyMs / N_ITERATIONS,
1076 childTiming.madviseMs / N_ITERATIONS};
1077}
1078
1079// ---------------------------------------------------------------------------
1080// Run one scenario and print results
1081// ---------------------------------------------------------------------------
1082static void runScenario(const Scenario& scenario)
1083{
1084 const auto& sizes = scenario.sizes;
1085 int nMessages = static_cast<int>(sizes.size());
1086 size_t totalBytes = totalPayloadSize(sizes);
1087 double totalMB = static_cast<double>(totalBytes) / (1024.0 * 1024.0);
1088
1089 printf("--------------------------------------------------------------\n");
1090 printf("Scenario: %s\n", scenario.name);
1091 printf(" Total payload: %.2f MB per TF\n", totalMB);
1092 printf(" Iterations: %d\n\n", N_ITERATIONS);
1093
1094 printf("Running FairMQ shmem benchmark...\n");
1095 auto resultA = benchmarkFairMQShmem(sizes);
1096
1097 printf("Running memfd+UDS benchmark...\n");
1098 auto resultB = benchmarkMemfdUDS(sizes);
1099
1100 printf("Running slab memfd benchmark...\n");
1101 auto resultC = benchmarkSlabMemfd(sizes);
1102
1103 double totalA = resultA.allocFillMs + resultA.sendMs + resultA.receiveMs;
1104 double throughputA = totalMB / (totalA / 1000.0);
1105
1106 double senderB = resultB.memfdCreateMs + resultB.senderMmapMs + resultB.fillMs + resultB.sendMs + resultB.senderUnmapMs;
1107 double receiverB = resultB.recvMs + resultB.receiverMmapMs + resultB.verifyMs + resultB.receiverUnmapMs;
1108 double totalB = senderB + receiverB;
1109 double throughputB = totalMB / (totalB / 1000.0);
1110
1111 printf("\n=== FairMQ shmem (%d iterations, %d messages/TF) ===\n",
1112 N_ITERATIONS, nMessages);
1113 printf(" Alloc+Fill: %.2f ms/TF\n", resultA.allocFillMs);
1114 printf(" Send: %.2f ms/TF\n", resultA.sendMs);
1115 printf(" Receive: %.2f ms/TF\n", resultA.receiveMs);
1116 printf(" Total: %.2f ms/TF\n", totalA);
1117 printf(" Throughput: %.2f GB/s\n", throughputA / 1024.0);
1118
1119 printf("\n=== memfd + bump + UDS (%d iterations, %d messages/TF) ===\n",
1120 N_ITERATIONS, nMessages);
1121 printf(" Sender breakdown:\n");
1122 printf(" memfd_create: %.2f ms/TF\n", resultB.memfdCreateMs);
1123 printf(" mmap: %.2f ms/TF\n", resultB.senderMmapMs);
1124 printf(" fill: %.2f ms/TF\n", resultB.fillMs);
1125 printf(" sendmsg: %.2f ms/TF\n", resultB.sendMs);
1126 printf(" munmap+close: %.2f ms/TF\n", resultB.senderUnmapMs);
1127 printf(" subtotal: %.2f ms/TF\n", senderB);
1128 printf(" Receiver breakdown:\n");
1129 printf(" recvmsg: %.2f ms/TF\n", resultB.recvMs);
1130 printf(" mmap: %.2f ms/TF\n", resultB.receiverMmapMs);
1131 printf(" verify: %.2f ms/TF\n", resultB.verifyMs);
1132 printf(" munmap+close: %.2f ms/TF\n", resultB.receiverUnmapMs);
1133 printf(" subtotal: %.2f ms/TF\n", receiverB);
1134 printf(" Total: %.2f ms/TF\n", totalB);
1135 printf(" Throughput: %.2f GB/s\n", throughputB / 1024.0);
1136
1137 printf("\nSpeedup (memfd vs FairMQ): %.1fx\n", totalA / totalB);
1138
1139 double senderC = resultC.fillMs + resultC.sendManifestMs;
1140 double receiverC = resultC.recvManifestMs + resultC.verifyMs + resultC.madviseMs;
1141 double totalC = senderC + receiverC;
1142 double throughputC = totalMB / (totalC / 1000.0);
1143
1144 printf("\n=== slab memfd + oldest-TF madvise (%d iterations, %d messages/TF, %d slabs x %zuMB) ===\n",
1145 N_ITERATIONS, nMessages, N_SLABS, SLAB_SIZE / (1024 * 1024));
1146 printf(" Sender breakdown:\n");
1147 printf(" fill: %.2f ms/TF\n", resultC.fillMs);
1148 printf(" send manifest:%.2f ms/TF\n", resultC.sendManifestMs);
1149 printf(" subtotal: %.2f ms/TF\n", senderC);
1150 printf(" Receiver breakdown:\n");
1151 printf(" recv manifest:%.2f ms/TF\n", resultC.recvManifestMs);
1152 printf(" verify: %.2f ms/TF\n", resultC.verifyMs);
1153 printf(" madvise: %.2f ms/TF\n", resultC.madviseMs);
1154 printf(" subtotal: %.2f ms/TF\n", receiverC);
1155 printf(" Total: %.2f ms/TF\n", totalC);
1156 printf(" Throughput: %.2f GB/s\n", throughputC / 1024.0);
1157
1158 printf("\nSpeedup (slab vs FairMQ): %.1fx\n\n", totalA / totalC);
1159}
1160
1161// ---------------------------------------------------------------------------
1162// Main
1163// ---------------------------------------------------------------------------
1164int main()
1165{
1166 printf("Benchmark: FairMQ shmem vs memfd+UDS\n\n");
1167
1168 auto scenario1 = makeManySmallScenario();
1169 auto scenario2 = makeFewLargeScenario();
1170
1171 runScenario(scenario1);
1172 runScenario(scenario2);
1173
1174 return 0;
1175}
int32_t i
uint16_t pid
Definition RawData.h:2
std::chrono::high_resolution_clock Clock
GLdouble n
Definition glcorearb.h:1982
const GLfloat * m
Definition glcorearb.h:4066
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint entry
Definition glcorearb.h:5735
GLsizeiptr size
Definition glcorearb.h:659
GLuint GLuint end
Definition glcorearb.h:469
const GLdouble * v
Definition glcorearb.h:832
GLuint GLsizei const GLuint const GLintptr const GLsizeiptr * sizes
Definition glcorearb.h:2595
GLuint const GLchar * name
Definition glcorearb.h:781
GLintptr offset
Definition glcorearb.h:660
GLbitfield flags
Definition glcorearb.h:1570
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t0
Definition glcorearb.h:5034
GLuint start
Definition glcorearb.h:469
GLenum GLuint GLenum GLsizei const GLchar * buf
Definition glcorearb.h:2514
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t1
Definition glcorearb.h:5034
uint8_t itsSharedClusterMap uint8_t
DeliveryType read(const std::string &str)
Polygon< T > close(Polygon< T > polygon)
Definition Polygon.h:126
void align(gsl::span< ElinkEncoder< BareFormat, CHARGESUM > > elinks)
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
ManifestEntry entries[MAX_MESSAGES]
std::vector< size_t > sizes
ManifestEntry entries[MAX_MESSAGES]
uint64_t const void const *restrict const msg
Definition x9.h:153