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}