mirror of
https://github.com/allaunthefox/SilverSight.git
synced 2026-08-07 22:15:46 +00:00
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>
319 lines
10 KiB
C
319 lines
10 KiB
C
/*
|
||
* epyc_oisc_cacheline_bench.c
|
||
*
|
||
* Cache-line-granular OISC benchmark for EPYC 9645 Turin.
|
||
*
|
||
* Three models, each building on the last:
|
||
*
|
||
* 1. WORD-SUBLEQ — traditional subleq on int16 words (baseline)
|
||
* 2. CL-SUBLEQ — subleq on 64-byte cache lines via AVX-512 aligned load/store
|
||
* 3. PCIE-TLP — each OISC instr = 64-byte PCIe TLP from a virtio-style
|
||
* ring buffer; measures raw TLP throughput
|
||
*
|
||
* The model: a cache-native OISC pipeline hooks PCIe signal hooks (TLP
|
||
* transactions) as its instruction fetch / memory access path. Every
|
||
* instruction is one or more 64-byte cache-line reads/writes — exactly
|
||
* what PCIe Gen5 x16 delivers as a single transaction.
|
||
*
|
||
* Build:
|
||
* gcc -march=znver5 -O3 -flto -mavx512f -mavx512bw \
|
||
* epyc_oisc_cacheline_bench.c -o oisc_cl_bench
|
||
*
|
||
* Run:
|
||
* perf stat -e cycles,instructions,cache-references,cache-misses,\
|
||
* L1-dcache-load-misses,LLC-load-misses,branch-misses \
|
||
* ./oisc_cl_bench
|
||
*/
|
||
|
||
#define _GNU_SOURCE
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <stdint.h>
|
||
#include <string.h>
|
||
#include <time.h>
|
||
#include <stdalign.h>
|
||
#include <immintrin.h>
|
||
|
||
/* ================================================================
|
||
* Common constants
|
||
* ================================================================ */
|
||
#define CL_SIZE 64 /* x86 cache line */
|
||
#define CL_WORDS (CL_SIZE / sizeof(int16_t)) /* 32 int16 per cache line */
|
||
#define N_LINES 4096 /* 4096 cache lines = 256 KB workspace */
|
||
#define MEM_SIZE (N_LINES * CL_SIZE)
|
||
#define HALT 0x8000
|
||
|
||
/* ================================================================
|
||
* 1. WORD-SUBLEQ (baseline)
|
||
* mem[dst] -= mem[src]; if mem[dst] <= 0 → pc = next; else pc += 3
|
||
* Word-aligned, no padding.
|
||
* ================================================================ */
|
||
static int64_t bench_word_subleq(int16_t *mem, int pc, int N) {
|
||
int16_t src, dst, next;
|
||
int64_t iters = 0;
|
||
int cnt = 0;
|
||
|
||
while (cnt < N) {
|
||
src = mem[pc];
|
||
dst = mem[pc + 1];
|
||
next = mem[pc + 2];
|
||
if (next == HALT) break;
|
||
mem[dst] -= mem[src];
|
||
pc = (mem[dst] <= 0) ? next : pc + 3;
|
||
iters++;
|
||
cnt++;
|
||
}
|
||
return iters;
|
||
}
|
||
|
||
/* Build a word-SUBLEQ program that executes ~N iterations.
|
||
* Returns pc of first instruction. */
|
||
static int build_word_prog(int16_t *mem, int N, int *out_pc) {
|
||
memset(mem, 0, MEM_SIZE);
|
||
|
||
/* Layout:
|
||
* line 0: constants (zero=0, neg1=-1, one=1, limit=N/64)
|
||
* line 1: program (src,dst,next triples)
|
||
* line 2+: workspace
|
||
*/
|
||
mem[0] = 0; /* zero addr */
|
||
mem[1] = -1; /* neg1 */
|
||
mem[2] = 1; /* one */
|
||
mem[3] = N; /* limit */
|
||
|
||
int cnt_addr = 64; /* counter at word 64 */
|
||
int tmp_addr = 65; /* temp at word 65 */
|
||
mem[cnt_addr] = 0;
|
||
mem[tmp_addr] = 0;
|
||
|
||
/*
|
||
* Loop:
|
||
* I0: cnt -= neg1 (cnt++)
|
||
* I1: tmp -= cnt (tmp = N - cnt)
|
||
* I2: branch if tmp <= 0
|
||
*/
|
||
|
||
int pc = 100; /* program at word 100 */
|
||
*out_pc = pc;
|
||
|
||
/* I0: cnt += 1 (cnt -= neg1, where neg1 = -1) */
|
||
mem[pc] = 1; /* src = addr 1 = neg1 */
|
||
mem[pc + 1] = cnt_addr;
|
||
mem[pc + 2] = -1; /* fall through */
|
||
pc += 3;
|
||
|
||
/* I1: tmp = N - cnt (tmp -= cnt, tmp starts = N at line 0) */
|
||
mem[pc] = cnt_addr;
|
||
mem[pc + 1] = tmp_addr;
|
||
mem[pc + 2] = pc + 3; /* fall through to I2 */
|
||
pc += 3;
|
||
|
||
/* I2: if tmp <= 0 → halt; else → loop */
|
||
/* tmp <= 0 means we ran N iterations */
|
||
mem[pc] = 0; /* src = zero (no-op for tmp) */
|
||
mem[pc + 1] = tmp_addr;
|
||
mem[pc + 2] = *out_pc; /* loop back */
|
||
pc += 3;
|
||
|
||
/* HALT */
|
||
mem[pc] = 0;
|
||
mem[pc + 1] = 0;
|
||
mem[pc + 2] = HALT;
|
||
|
||
return pc + 3; /* total words used */
|
||
}
|
||
|
||
/* ================================================================
|
||
* 2. CL-SUBLEQ (cache-line granular)
|
||
*
|
||
* Each "word" in this variant is a 64-byte cache line. The SUBLEQ
|
||
* instruction becomes:
|
||
*
|
||
* line[dst] := line[dst] − line[src] (elementwise, via AVX-512)
|
||
* if all(line[dst]) <= 0 → pc = next; else pc += 3
|
||
*
|
||
* "All zeros" is checked as: the first int16 of a cache line ≤ 0.
|
||
* For a real pipeline, this would be a SIMD compare + mask test.
|
||
* ================================================================ */
|
||
typedef int64_t cacheline_t[CL_WORDS] __attribute__((aligned(64)));
|
||
|
||
static inline void cl_sub(cacheline_t *a, cacheline_t *b) {
|
||
/* b[] -= a[] using AVX-512 */
|
||
__m512i va = _mm512_load_si512(a);
|
||
__m512i vb = _mm512_load_si512(b);
|
||
__m512i vr = _mm512_sub_epi16(vb, va);
|
||
_mm512_store_si512(b, vr);
|
||
}
|
||
|
||
static inline int cl_is_nonpositive(cacheline_t *b) {
|
||
/* Return 1 if first element <= 0 (proxy for "all zero") */
|
||
return (*b)[0] <= 0;
|
||
}
|
||
|
||
static int64_t bench_cl_subleq(cacheline_t *mem, int pc, int N) {
|
||
int16_t src, dst, next;
|
||
int64_t iters = 0;
|
||
int cnt = 0;
|
||
|
||
while (cnt < N) {
|
||
/* The "program" is still stored as int16 triples in line 0 */
|
||
src = ((int16_t *)mem)[pc];
|
||
dst = ((int16_t *)mem)[pc + 1];
|
||
next = ((int16_t *)mem)[pc + 2];
|
||
if (next == HALT) break;
|
||
|
||
cl_sub(&mem[src], &mem[dst]);
|
||
|
||
pc = cl_is_nonpositive(&mem[dst]) ? next : pc + 3;
|
||
iters++;
|
||
cnt++;
|
||
}
|
||
return iters;
|
||
}
|
||
|
||
/* ================================================================
|
||
* 3. PCIE-TLP model
|
||
*
|
||
* Simulates PCIe Transaction Layer Packets as the instruction transport.
|
||
* Each TLP = 64 bytes (one cache line):
|
||
* [src_line:16] [dst_line:16] [next_line:16] [flags:16] [payload: 56 B pad]
|
||
*
|
||
* The OISC engine reads TLPs from a "RX ring" (pre-allocated buffer),
|
||
* executes the SUBLEQ on cache lines, and writes result TLPs to a
|
||
* "TX ring."
|
||
*
|
||
* Metric: TLPs processed per second = cache lines / sec.
|
||
* At PCIe Gen5 x16 (64 GT/s, 128B/130B encoding) = ~63 GB/s raw.
|
||
* Each TLP read+write = 2 × 64B = 128B per instruction.
|
||
* Theoretical max: ~492 M TLPs/sec per PCIe Gen5 x16 lane pair.
|
||
*
|
||
* This benchmark measures the software-side bottleneck.
|
||
* ================================================================ */
|
||
typedef struct __attribute__((packed, aligned(64))) {
|
||
uint16_t src_line;
|
||
uint16_t dst_line;
|
||
uint16_t next_line;
|
||
uint16_t flags;
|
||
uint8_t pad[56]; /* fill to 64 B */
|
||
} PcieTlp;
|
||
|
||
/* Generate a batch of TLPs in a ring buffer */
|
||
static int generate_tlps(PcieTlp *ring, int count, int src_line,
|
||
int dst_line, int next_line) {
|
||
for (int i = 0; i < count; i++) {
|
||
ring[i].src_line = src_line;
|
||
ring[i].dst_line = dst_line;
|
||
ring[i].next_line = next_line;
|
||
ring[i].flags = 0;
|
||
memset(ring[i].pad, 0, 56);
|
||
}
|
||
return count;
|
||
}
|
||
|
||
/* Execute a TLP stream against a cache-line memory, as PCIe RX→process→TX */
|
||
static int64_t bench_pcie_tlp(PcieTlp *rx_ring, int n_tlps,
|
||
cacheline_t *mem) {
|
||
int64_t processed = 0;
|
||
|
||
for (int i = 0; i < n_tlps; i++) {
|
||
PcieTlp *tlp = &rx_ring[i];
|
||
|
||
/* SUBLEQ on cache lines */
|
||
cl_sub(&mem[tlp->src_line], &mem[tlp->dst_line]);
|
||
|
||
/* "Write-back" — mark TLP as processed (simulates TX completion) */
|
||
tlp->flags = 1;
|
||
processed++;
|
||
}
|
||
return processed;
|
||
}
|
||
|
||
/* ================================================================
|
||
* Timing helper
|
||
* ================================================================ */
|
||
static double now_sec(void) {
|
||
struct timespec ts;
|
||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
return ts.tv_sec + ts.tv_nsec * 1e-9;
|
||
}
|
||
|
||
/* ================================================================
|
||
* Main
|
||
* ================================================================ */
|
||
int main(void) {
|
||
printf("=== EPYC 9645 Turin — Cache-Line OISC Benchmark ===\n\n");
|
||
|
||
/* ---------- 1. WORD SUBLEQ ---------- */
|
||
printf("--- 1. WORD-SUBLEQ (baseline) ---\n");
|
||
int16_t *word_mem = aligned_alloc(64, MEM_SIZE);
|
||
int word_pc;
|
||
build_word_prog(word_mem, 10000000, &word_pc);
|
||
|
||
double t0 = now_sec();
|
||
int64_t wi = bench_word_subleq(word_mem, word_pc, 10000000);
|
||
double t1 = now_sec();
|
||
double ws = wi / (t1 - t0) / 1e6;
|
||
printf(" %ld iter in %.4f sec = %.2f M instr/sec\n",
|
||
(long)wi, t1 - t0, ws);
|
||
printf(" Words touched/sec: %.2f M\n", ws * 6); /* 6 word accesses per instr */
|
||
free(word_mem);
|
||
|
||
/* ---------- 2. CL SUBLEQ ---------- */
|
||
printf("\n--- 2. CL-SUBLEQ (cache-line granular, AVX-512) ---\n");
|
||
cacheline_t *cl_mem = aligned_alloc(64, MEM_SIZE);
|
||
|
||
/* Build program in cl_mem[0] as int16 triples */
|
||
int cl_pc;
|
||
build_word_prog((int16_t *)cl_mem, 10000000, &cl_pc);
|
||
|
||
/* Warmup */
|
||
bench_cl_subleq(cl_mem, cl_pc, 1000);
|
||
build_word_prog((int16_t *)cl_mem, 10000000, &cl_pc);
|
||
|
||
t0 = now_sec();
|
||
int64_t ci = bench_cl_subleq(cl_mem, cl_pc, 1000000);
|
||
t1 = now_sec();
|
||
double cs = ci / (t1 - t0) / 1e6;
|
||
printf(" %ld iter in %.4f sec = %.2f M instr/sec\n",
|
||
(long)ci, t1 - t0, cs);
|
||
printf(" Cache lines touched/sec: %.2f M\n", cs * 2);
|
||
free(cl_mem);
|
||
|
||
/* ---------- 3. PCIE TLP ---------- */
|
||
printf("\n--- 3. PCIE-TLP MODEL (cache-line TLP stream) ---\n");
|
||
|
||
int n_tlps = 100000;
|
||
PcieTlp *rx_ring = aligned_alloc(64, n_tlps * sizeof(PcieTlp));
|
||
cacheline_t *tlp_mem = aligned_alloc(64, MEM_SIZE);
|
||
|
||
/* Set up: zero memory, generate TLP stream */
|
||
memset(tlp_mem, 0, MEM_SIZE);
|
||
generate_tlps(rx_ring, n_tlps, 2, 3, HALT);
|
||
/* line 2 = zero, line 3 = zero. cl_sub(zero, zero) = no-op. */
|
||
|
||
/* Warmup */
|
||
bench_pcie_tlp(rx_ring, 1000, tlp_mem);
|
||
|
||
t0 = now_sec();
|
||
int64_t pi = bench_pcie_tlp(rx_ring, n_tlps, tlp_mem);
|
||
t1 = now_sec();
|
||
double ps = pi / (t1 - t0) / 1e6;
|
||
printf(" %ld TLPs in %.4f sec = %.2f M TLPs/sec\n",
|
||
(long)pi, t1 - t0, ps);
|
||
printf(" PCIe BW equivalent: %.2f GB/s (2 × 64B per TLP)\n",
|
||
ps * 128.0 / 1000.0);
|
||
/* Each TLP = 64B read + 64B write = 128B */
|
||
free(rx_ring);
|
||
free(tlp_mem);
|
||
|
||
/* ---------- SUMMARY ---------- */
|
||
printf("\n=== SUMMARY ===\n");
|
||
printf(" %-30s %12.2f M ops/sec\n", "WORD-SUBLEQ", ws);
|
||
printf(" %-30s %12.2f M ops/sec\n", "CL-SUBLEQ (AVX-512)", cs);
|
||
printf(" %-30s %12.2f M ops/sec\n", "PCIE-TLP (cache line)", ps);
|
||
printf("\n TLP → PCIe Gen5 x16 raw: ~63 GB/s\n");
|
||
printf(" TLP → PCIe Gen5 x8 raw: ~31 GB/s\n");
|
||
printf(" TLP → PCIe Gen4 x16 raw: ~31 GB/s\n");
|
||
|
||
return 0;
|
||
}
|