SilverSight/experiments/epyc_oisc_bench.c
allaun 3b6baec64e wip: durability snapshot of local working tree (pre-existing, uncommitted)
Snapshot of previously-uncommitted local work so nothing is lost after the
power outage. NOT reviewed for correctness — a WIP checkpoint, not a feature:
- multi-language hachimoji encoders (c/cpp/fortran/julia/octave/r/scala/go/rust/coq)
- formal Lean WIP (BraidTree, Eisenstein, HachimojiCapture, MathlibConnect,
  ModularFormBridge, ClusterManifold) + lakefile + E8Sidon edit
- docs/, experiments/ (epyc oisc benches), deploy/, scripts, test scaffolding
- .gitignore: exclude **/target/ and Coq build artifacts

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:49:53 -05:00

242 lines
8.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* epyc_oisc_bench.c — EPYC 9645 Turin OISC throughput
*
* Three independent benchmarks:
* 1. Word SUBLEQ (int16, standard)
* 2. Cache-line SUBLEQ (AVX-512, each variable on its own cache line)
* 3. Ring dispatch (virtio/TLP batch model)
*
* Build: gcc -march=znver5 -O3 -flto -mavx512f -mavx512bw epyc_oisc_bench.c -o oisc_bench
* Run: perf stat ./oisc_bench
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdalign.h>
#include <time.h>
#include <immintrin.h>
#define HALT ((int16_t)0x8000)
#define WORDS 65536
static double now(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec + ts.tv_nsec * 1e-9;
}
/* ═══════════════════════════════════════════════════════════════
* 1. WORD SUBLEQ
* ═══════════════════════════════════════════════════════════════ */
#define W_ONE 1 /* addr 1 = constant 1 */
#define W_CNT 2 /* addr 2 = counter (initialized to N) */
#define W_ACC 3 /* addr 3 = accumulator */
#define W_ZERO 0
static void build_word(int16_t *m, int N) {
memset(m, 0, WORDS * sizeof(int16_t));
m[W_ONE] = 1;
m[W_CNT] = N;
m[W_ACC] = 0;
/* Program at word 1024 */
int pc = 1024;
int looptop = pc;
/* I0: CNT--; if CNT <= 0 → HALT */
m[pc] = W_ONE; m[pc+1] = W_CNT; m[pc+2] = 0; /* 0 = patch later */
pc += 3;
/* I1: ZERO=0, always branch back */
m[pc] = W_ZERO; m[pc+1] = W_ZERO; m[pc+2] = looptop;
pc += 3;
int haltpc = pc;
m[pc] = 0; m[pc+1] = 0; m[pc+2] = HALT;
/* Patch I0's HALT target */
m[looptop + 2] = haltpc;
}
static int64_t run_word(int16_t *m, int pc) {
int64_t n = 0;
while (1) {
int16_t s = m[pc], d = m[pc+1], nx = m[pc+2];
if (nx == HALT) break;
m[d] -= m[s];
pc = (m[d] <= 0) ? nx : pc + 3;
n++;
}
return n;
}
/* ═══════════════════════════════════════════════════════════════
* 2. CACHE-LINE SUBLEQ (AVX-512)
*
* Each variable lives on its own 64-byte cache line:
* CL_ONE = line 1 (word 32 = 1, rest = 0)
* CL_CNT = line 2 (word 64 = N, rest = 0)
* CL_ACC = line 3 (word 96 = 0, rest = 0)
* CL_ZERO = line 0 (all zeros)
*
* Program addresses refer to LINE indices (0-4095).
* Execution: cl_sub(&cm[src_line], &cm[dst_line]) (entire line subtract)
* Check: if first word of dst line <= 0 → branch
* ═══════════════════════════════════════════════════════════════ */
#define CL_ONE 1
#define CL_CNT 2
#define CL_ACC 3
#define CL_ZERO 0
typedef int16_t cl_line_t[32] __attribute__((aligned(64)));
static inline void cl_sub(cl_line_t *a, cl_line_t *b) {
__m512i va = _mm512_load_si512(a);
__m512i vb = _mm512_load_si512(b);
_mm512_store_si512(b, _mm512_sub_epi16(vb, va));
}
static void build_cl(cl_line_t *cm, int N) {
memset(cm, 0, 4096 * sizeof(cl_line_t));
/* Init cache lines */
((int16_t *)(&cm[CL_ONE]))[0] = 1;
((int16_t *)(&cm[CL_CNT]))[0] = N;
((int16_t *)(&cm[CL_ACC]))[0] = 0;
/* Program stored as int16 triples after the last cache line (word 131072+) */
/* We'll use word 131072 and up for program storage */
int16_t *prog = (int16_t *)&cm[2048]; /* after cache line 2048 = 128 KB */
int pc = 0;
int looptop = 0;
/* I0: CL_CNT -= CL_ONE; if CL_CNT[0] <= 0 → HALT */
prog[pc] = CL_ONE; prog[pc+1] = CL_CNT; prog[pc+2] = 0; /* patch later */
pc += 3;
/* I1: CL_ZERO = 0 → always branch back */
prog[pc] = CL_ZERO; prog[pc+1] = CL_ZERO; prog[pc+2] = looptop;
pc += 3;
int haltpc = pc;
prog[pc] = 0; prog[pc+1] = 0; prog[pc+2] = HALT;
prog[looptop + 2] = haltpc;
}
static int64_t run_cl(cl_line_t *cm, int entry) {
int16_t *prog = (int16_t *)&cm[2048];
int pc = entry;
int64_t n = 0;
while (1) {
int16_t s = prog[pc], d = prog[pc+1], nx = prog[pc+2];
if (nx == HALT) break;
cl_sub(&cm[s], &cm[d]);
pc = (((int16_t *)(&cm[d]))[0] <= 0) ? nx : pc + 3;
n++;
}
return n;
}
/* ═══════════════════════════════════════════════════════════════
* 3. RING DISPATCH (virtio/TLP model)
*
* Pre-encoded instructions in a ring buffer (64B each, like PCIe TLPs).
* Each TLP = RingInstr { src, dst, nxt, pad_to_64B }.
* Processor just reads ring entries in sequence.
* ═══════════════════════════════════════════════════════════════ */
typedef struct __attribute__((aligned(64))) {
int16_t src;
int16_t dst;
int16_t nxt;
int16_t _pad[29];
} RingInstr;
static void build_ring(RingInstr *ring, int count) {
for (int i = 0; i < count; i++) {
ring[i].src = 3; /* ACC */
ring[i].dst = 4; /* WRK */
ring[i].nxt = (i + 1 < count) && (i % 100000 != 99999) ? (int16_t)(i + 1) : HALT;
memset(ring[i]._pad, 0, 58);
}
}
static int64_t run_ring(RingInstr *ring, int count, int16_t *mem) {
int64_t n = 0;
for (int i = 0; i < count; i++) {
if (ring[i].nxt == HALT) break;
mem[ring[i].dst] -= mem[ring[i].src];
n++;
}
return n;
}
/* ═══════════════════════════════════════════════════════════════
* Main
* ═══════════════════════════════════════════════════════════════ */
int main(void) {
printf("=== EPYC 9645 Turin — OISC Cache-Line Benchmark ===\n\n");
FILE *f = fopen("/proc/cpuinfo", "r");
char buf[256];
if (f) {
while (fgets(buf, sizeof buf, f))
if (strstr(buf, "model name") || strstr(buf, "cache size")) {
buf[strcspn(buf, "\n")] = 0; printf(" %s\n", buf);
}
fclose(f);
}
int N = 30000;
int64_t total, tw, tc, tr;
double t0, t1, sw, sc, sr;
/* ──────────── WORD ──────────── */
printf("\n── 1. WORD SUBLEQ (%d iter × 2000 runs) ──\n", N);
int16_t *wm = aligned_alloc(64, WORDS * sizeof(int16_t));
build_word(wm, N);
run_word(wm, 1024);
build_word(wm, N);
t0 = now(); total = 0;
for (int i = 0; i < 2000; i++) { build_word(wm, N); total += run_word(wm, 1024); }
t1 = now(); tw = total; sw = tw / (t1 - t0) / 1e6;
printf(" %ld instr in %.4f s = %.2f M/s\n", (long)tw, t1 - t0, sw);
free(wm);
/* ──────────── CACHE LINE ──────────── */
printf("\n── 2. CACHE-LINE SUBLEQ (AVX-512, %d iter × 2000 runs) ──\n", N);
cl_line_t *cm = aligned_alloc(64, 4096 * sizeof(cl_line_t));
build_cl(cm, N);
run_cl(cm, 0);
build_cl(cm, N);
t0 = now(); total = 0;
for (int i = 0; i < 2000; i++) { build_cl(cm, N); total += run_cl(cm, 0); }
t1 = now(); tc = total; sc = tc / (t1 - t0) / 1e6;
printf(" %ld instr in %.4f s = %.2f M/s\n", (long)tc, t1 - t0, sc);
free(cm);
/* ──────────── RING ──────────── */
printf("\n── 3. RING DISPATCH (virtio/TLP, 65536 × 1000) ──\n");
RingInstr *ring = aligned_alloc(64, 65536 * sizeof(RingInstr));
int16_t *rm = aligned_alloc(64, 1024 * sizeof(int16_t));
memset(rm, 0, 1024 * sizeof(int16_t));
rm[3] = 0; rm[4] = 0;
build_ring(ring, 65536);
t0 = now(); total = 0;
for (int i = 0; i < 1000; i++) { total += run_ring(ring, 65536, rm); }
t1 = now(); tr = total; sr = tr / (t1 - t0) / 1e6;
printf(" %ld instr in %.4f s = %.2f M/s\n", (long)tr, t1 - t0, sr);
free(ring); free(rm);
/* ──────────── SUMMARY ──────────── */
printf("\n═══ SUMMARY ═══\n");
printf(" %-28s %9.2f M instr/s\n", "WORD SUBLEQ", sw);
printf(" %-28s %9.2f M instr/s\n", "CACHE-LINE (AVX-512)", sc);
printf(" %-28s %9.2f M instr/s\n", "RING DISPATCH", sr);
printf("\n Ratio CL/Word: %.2fx\n", sc / sw);
printf(" Ratio Ring/Word: %.2fx\n", sr / sw);
return 0;
}