Files

290 lines
9.2 KiB
C
Executable File

/**
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <sys/time.h>
/* ── 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;
}