/** * bench_tcp.c — TCP stack throughput benchmark (standalone, no DPDK) * * Tests the critical data path: ring buffer + fragment alloc + memcpy * This is the CPU-bound portion of the TCP stack. * * Compile: gcc -O2 -o bench_tcp bench_tcp.c -lpthread * Run: ./bench_tcp */ #include #include #include #include #include #include /* ── Simulated ring buffer (same logic as DPDK rte_ring) ── */ #define RING_SIZE 1024 #define RING_MASK (RING_SIZE - 1) typedef struct { void *buf[RING_SIZE]; uint32_t head __attribute__((aligned(64))); uint32_t tail __attribute__((aligned(64))); } ring_t; static inline int ring_enqueue(ring_t *r, void *obj) { uint32_t next = (r->head + 1) & RING_MASK; if (next == r->tail) return -1; r->buf[r->head] = obj; __sync_synchronize(); r->head = next; return 0; } static inline int ring_dequeue(ring_t *r, void **obj) { if (r->head == r->tail) return -1; __sync_synchronize(); *obj = r->buf[r->tail]; r->tail = (r->tail + 1) & RING_MASK; return 0; } /* ── TCP fragment (same as production code) ── */ typedef struct { uint16_t sport, dport; uint32_t seqnum, acknum; uint8_t hdrlen_off, tcp_flags; uint16_t windows, cksum, tcp_urp; int optlen; uint32_t option[10]; unsigned char *data; int length; } tcp_fragment_t; /* ── Simple hash table (256 buckets, linked list) ── */ #define HT_SIZE 256 #define HT_MASK 255 typedef struct ht_entry { uint32_t key; int val; struct ht_entry *next; } ht_entry_t; static ht_entry_t *htable[HT_SIZE]; static inline uint32_t ht_hash(uint32_t k) { return (k ^ (k >> 16)) & HT_MASK; } static void ht_insert(uint32_t key, int val) { uint32_t h = ht_hash(key); ht_entry_t *e = malloc(sizeof(*e)); e->key = key; e->val = val; e->next = htable[h]; htable[h] = e; } static int ht_lookup(uint32_t key) { uint32_t h = ht_hash(key); ht_entry_t *e; for (e = htable[h]; e; e = e->next) if (e->key == key) return e->val; return -1; } /* ── Timer helpers ── */ static double now_us(void) { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * 1000000.0 + tv.tv_usec; } /* ═══════════════════════════════════════════════════════════════ */ static void bench_ring_sp_sc(void) { printf("\n=== Ring Buffer SP/SC Throughput ===\n"); ring_t *rx = calloc(1, sizeof(ring_t)); ring_t *tx = calloc(1, sizeof(ring_t)); /* Pre-allocate fragments */ #define N_FRAGS 512 tcp_fragment_t *frags[N_FRAGS]; for (int i = 0; i < N_FRAGS; i++) { frags[i] = calloc(1, sizeof(tcp_fragment_t)); frags[i]->data = malloc(1460); frags[i]->length = 1460; memset(frags[i]->data, 'A', 1460); } int iterations = 1000000; double start = now_us(); /* Producer-Consumer: enqueue N, dequeue N, repeat */ int produced = 0, consumed = 0; int fi = 0; for (int i = 0; i < iterations; i++) { if (ring_enqueue(rx, frags[fi % N_FRAGS]) == 0) { produced++; fi++; } void *obj = NULL; if (ring_dequeue(rx, &obj) == 0) { consumed++; /* Simulate processing: checksum + copy */ tcp_fragment_t *f = (tcp_fragment_t *)obj; volatile uint32_t cksum = 0; for (int j = 0; j < f->length; j += 4) cksum += *(uint32_t*)(f->data + j); (void)cksum; ring_enqueue(tx, f); } void *tx_obj = NULL; if (ring_dequeue(tx, &tx_obj) == 0) { /* TX done */ } } double end = now_us(); double sec = (end - start) / 1000000.0; uint64_t total_bytes = (uint64_t)produced * 1460; printf(" Produced: %d fragments\n", produced); printf(" Consumed: %d fragments\n", consumed); printf(" Total data: %.1f MB\n", total_bytes / 1e6); printf(" Time: %.2f ms\n", sec * 1000.0); printf(" Throughput: %.2f Gbps\n", (total_bytes * 8.0) / (sec * 1e9)); printf(" PPS: %.0f Kpps\n", produced / (sec * 1000.0)); printf(" Latency: %.0f ns/frag\n", sec * 1e9 / produced); for (int i = 0; i < N_FRAGS; i++) { free(frags[i]->data); free(frags[i]); } free(rx); free(tx); } static void bench_hash(void) { printf("\n=== Hash Table Lookup (256 buckets, 1000 entries) ===\n"); /* Populate */ for (int i = 0; i < 1000; i++) ht_insert((uint32_t)i * 2654435761U, i); int iterations = 10000000; int found = 0; double start = now_us(); for (int i = 0; i < iterations; i++) { uint32_t key = (uint32_t)(i % 1000) * 2654435761U; if (ht_lookup(key) >= 0) found++; } double end = now_us(); double ns_per = (end - start) * 1000.0 / iterations; printf(" Entries: 1000\n"); printf(" Lookups: %d (all found)\n", found); printf(" Time: %.1f ns/lookup\n", ns_per); printf(" Rate: %.0f Mlookups/sec\n", 1000.0 / ns_per); /* Cleanup */ for (int i = 0; i < HT_SIZE; i++) { ht_entry_t *e = htable[i]; while (e) { ht_entry_t *n = e->next; free(e); e = n; } htable[i] = NULL; } } static void bench_ws_frame(void) { printf("\n=== WebSocket Frame Encode/Decode ===\n"); uint8_t payload[1460]; uint8_t frame[2048]; uint8_t mask[4] = {0x12, 0x34, 0x56, 0x78}; memset(payload, 'X', 1460); int iterations = 500000; double start = now_us(); for (int i = 0; i < iterations; i++) { /* Encode (client→server, MASKED) */ int pos = 0; frame[pos++] = 0x80 | 0x02; /* FIN=1, BINARY */ frame[pos++] = 0x80 | 126; /* MASK=1, 16-bit length */ frame[pos++] = (1460 >> 8) & 0xFF; frame[pos++] = 1460 & 0xFF; memcpy(frame + pos, mask, 4); pos += 4; for (int j = 0; j < 1460; j++) frame[pos + j] = payload[j] ^ mask[j % 4]; pos += 1460; /* Decode (server→client, verify) */ uint8_t op = frame[0] & 0x0F; int m = (frame[1] >> 7) & 1; int plen = frame[1] & 0x7F; if (plen == 126) plen = (frame[2] << 8) | frame[3]; uint8_t *mk = &frame[plen == 126 ? 4 : 2]; int data_off = plen == 126 ? 8 : 6; /* Server frames shouldn't be masked, but we check anyway */ if (m) for (int j = 0; j < plen; j++) frame[data_off + j] ^= mk[j % 4]; volatile int check = op + plen; (void)check; } double end = now_us(); double sec = (end - start) / 1e6; uint64_t total = (uint64_t)iterations * 1460 * 2; /* encode + decode */ printf(" Iterations: %d\n", iterations); printf(" Total data: %.1f MB\n", total / 1e6); printf(" Time: %.2f ms\n", sec * 1000); printf(" Throughput: %.2f Gbps\n", (total * 8.0) / (sec * 1e9)); } static void bench_memcpy(void) { printf("\n=== memcpy Throughput (baseline) ===\n"); int sizes[] = {64, 256, 1460, 4096, 65536}; for (int s = 0; s < 5; s++) { int sz = sizes[s]; char *src = malloc(sz); char *dst = malloc(sz); memset(src, 'A', sz); int iterations = (sz < 1024) ? 10000000 : (sz < 10000 ? 1000000 : 100000); double start = now_us(); for (int i = 0; i < iterations; i++) memcpy(dst, src, sz); double end = now_us(); double ns_per = (end - start) * 1000.0 / iterations; double gbps = (sz * 8.0) / (ns_per); printf(" %5d bytes: %6.1f ns/copy → %.1f Gbps\n", sz, ns_per, gbps); free(src); free(dst); } } /* ═══════════════════════════════════════════════════════════════ */ int main(void) { printf("╔══════════════════════════════════════════════╗\n"); printf("║ DPDK TCP Stack — Data Path Benchmark ║\n"); printf("║ (CPU-only, no NIC hardware needed) ║\n"); printf("╚══════════════════════════════════════════════╝\n"); bench_memcpy(); bench_hash(); bench_ring_sp_sc(); bench_ws_frame(); printf("\n╔══════════════════════════════════════════════╗\n"); printf("║ Expected DPDK Wire-Speed (with NIC HW): ║\n"); printf("║ ║\n"); printf("║ 10 Gbps line rate: ~9.5 Gbps TCP goodput║\n"); printf("║ 25 Gbps line rate: ~23.8 Gbps TCP ║\n"); printf("║ Single-core PPS: 5-8 Mpps (64B) ║\n"); printf("║ RTT (local): 5-15 us ║\n"); printf("╚══════════════════════════════════════════════╝\n"); return 0; }