290 lines
9.2 KiB
C
Executable File
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;
|
|
}
|