code wiki / (root) / nx_p256_fieldmul_3way_bench.nx

nx_p256_fieldmul_3way_bench.nx source

↩ module page · 94 lines · 5357 B

1// nx_p256_fieldmul_3way_bench.nx -- DEFINITIVE single-run decomposition of the P-256 field-mul win: 2// [0] production = u256_mul_wide (8x32 sw) + production Solinas 3// [1] mulx-only = fused __mul256_wide + production Solinas 4// [2] full-opt = fused __mul256_wide + deferred-carry fast Solinas 5// All three timed back-to-back on the same machine state against the SAME inputs, so the ratios are 6// directly comparable (kills the cross-run wall-clock variance). Correctness pre-check on both 7// variants. Dependent-chain (DCE/hoist-proof). Uses __mul256_wide -> nx_cand_build_run. 8// expect_exit: 0 license_tier: ORIGINAL 9import "nx_p256_solinas_fast.nx" // base p256_field_mul + _p256_solinas_reduce + _p256_solinas_reduce_fast 10const N_MAGIC_1000000000: i64 = 1000000000 11const N_MAGIC_6364136223846793005: i64 = 6364136223846793005 12const N_MAGIC_1442695040888963407: i64 = 1442695040888963407 13const N_MAGIC_2000: i64 = 2000 14 15const N_ITER: i64 = 3000000 16 17func bp(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 18func bn(v: i64) -> i64 { 19 let t: *u8 = sys_mmap(28); var m: i64 = v; if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m } 20 let b: *u8 = sys_mmap(28); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 } 21 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 22 var i: i64 = 0; while i < k { b[i] = t[k-1-i]; i = i + 1 } sys_write(1, b, k); return 0 23} 24func now_ns(ts: *i64) -> i64 { __syscall(SYS_CLOCK_GETTIME, 1, ts as i64, 0, 0, 0, 0); return ts[0] * N_MAGIC_1000000000 + ts[1] } 25func lcg(st: *i64) -> i64 { let x: i64 = st[0] * N_MAGIC_6364136223846793005 + N_MAGIC_1442695040888963407; st[0] = x; return x } 26 27func pack_split_mul(a: *i64, b: *i64, a64: *i64, b64: *i64, prod: *i64, c16: *i64) -> i64 { 28 var k: i64 = 0 29 while k < 4 { a64[k] = (a[2*k] & 0xffffffff) | ((a[2*k+1] & 0xffffffff) << 32); b64[k] = (b[2*k] & 0xffffffff) | ((b[2*k+1] & 0xffffffff) << 32); k = k + 1 } 30 __mul256_wide(prod, a64, b64) 31 k = 0; while k < 8 { c16[2*k] = prod[k] & 0xffffffff; c16[2*k+1] = (prod[k] >> 32) & 0xffffffff; k = k + 1 } 32 return 0 33} 34func fm_mulx(out8: *i64, a: *i64, b: *i64, a64: *i64, b64: *i64, prod: *i64, c16: *i64) -> i64 { 35 pack_split_mul(a, b, a64, b64, prod, c16); _p256_solinas_reduce(out8, c16); return 0 36} 37func fm_full(out8: *i64, a: *i64, b: *i64, a64: *i64, b64: *i64, prod: *i64, c16: *i64) -> i64 { 38 pack_split_mul(a, b, a64, b64, prod, c16); _p256_solinas_reduce_fast(out8, c16); return 0 39} 40 41func main() -> i64 { 42 let a: *i64 = sys_mmap(8*8) as *i64 43 let b: *i64 = sys_mmap(8*8) as *i64 44 let r: *i64 = sys_mmap(8*8) as *i64 45 let r2: *i64 = sys_mmap(8*8) as *i64 46 let ts: *i64 = sys_mmap(32) as *i64 47 let a64: *i64 = sys_mmap(4*8) as *i64 48 let b64: *i64 = sys_mmap(4*8) as *i64 49 let prod: *i64 = sys_mmap(8*8) as *i64 50 let c16: *i64 = sys_mmap(16*8) as *i64 51 52 // correctness: both variants == production over 2000 random field elements 53 let st: *i64 = sys_mmap(8) as *i64 54 st[0] = 0xc3d2e1f087b4a691 55 var bad: i64 = 0 56 var n: i64 = 0 57 while n < N_MAGIC_2000 { 58 var k: i64 = 0; while k < 8 { a[k] = lcg(st) & 0xffffffff; b[k] = lcg(st) & 0xffffffff; k = k + 1 } 59 p256_field_mul(r, a, b) 60 fm_mulx(r2, a, b, a64, b64, prod, c16); k = 0; while k < 8 { if (r[k]&0xffffffff)!=(r2[k]&0xffffffff){bad=bad+1} k=k+1 } 61 fm_full(r2, a, b, a64, b64, prod, c16); k = 0; while k < 8 { if (r[k]&0xffffffff)!=(r2[k]&0xffffffff){bad=bad+1} k=k+1 } 62 n = n + 1 63 } 64 bp("=== nx_p256_fieldmul_3way_bench: production vs mulx-only vs full-opt (same run) ===\n" as *u8) 65 if bad != 0 { bp("CORRECTNESS FAIL bad=" as *u8); bn(bad); bp("\n" as *u8); sys_exit(1); return 1 } 66 bp("correctness: mulx-only AND full-opt == production over 2000 vectors (GREEN)\n" as *u8) 67 68 var k: i64 = 0; while k < 8 { a[k] = (0x1111111100000001 * (k + 1)) & 0xffffffff; b[k] = (0xdeadbeef12345678 * (k + 3)) & 0xffffffff; k = k + 1 } 69 let a0i: i64 = a[0] 70 71 var acc0: i64 = 0 72 let p0: i64 = now_ns(ts) 73 k = 0; while k < N_ITER { a[0] = (a[0] ^ acc0) & 0xffffffff; p256_field_mul(r, a, b); acc0 = acc0 ^ r[0] ^ r[3] ^ r[7]; k = k + 1 } 74 let ns0: i64 = now_ns(ts) - p0 75 76 a[0] = a0i 77 var acc1: i64 = 0 78 let p1: i64 = now_ns(ts) 79 k = 0; while k < N_ITER { a[0] = (a[0] ^ acc1) & 0xffffffff; fm_mulx(r, a, b, a64, b64, prod, c16); acc1 = acc1 ^ r[0] ^ r[3] ^ r[7]; k = k + 1 } 80 let ns1: i64 = now_ns(ts) - p1 81 82 a[0] = a0i 83 var acc2: i64 = 0 84 let p2: i64 = now_ns(ts) 85 k = 0; while k < N_ITER { a[0] = (a[0] ^ acc2) & 0xffffffff; fm_full(r, a, b, a64, b64, prod, c16); acc2 = acc2 ^ r[0] ^ r[3] ^ r[7]; k = k + 1 } 86 let ns2: i64 = now_ns(ts) - p2 87 88 bn(N_ITER); bp(" iters\n" as *u8) 89 bp("[0] production = " as *u8); bn(ns0 / N_ITER); bp(" ns/op\n" as *u8) 90 bp("[1] mulx-only = " as *u8); bn(ns1 / N_ITER); bp(" ns/op speedup_x100=" as *u8); if ns1 > 0 { bn(ns0 * 100 / ns1) } bp("\n" as *u8) 91 bp("[2] full-opt = " as *u8); bn(ns2 / N_ITER); bp(" ns/op speedup_x100=" as *u8); if ns2 > 0 { bn(ns0 * 100 / ns2) } bp("\n" as *u8) 92 bp("checksums " as *u8); bn(acc0); bp(" " as *u8); bn(acc1); bp(" " as *u8); bn(acc2); bp(" (all EQUAL = identical computation)\n" as *u8) 93 return 0 94}