code wiki / (root) / sketch_space_saving_test.nx

sketch_space_saving_test.nx source

↩ module page · 120 lines · 4784 B

1// sketch_space_saving_test.nx -- SpaceSaving behavioral test. 2// 3// Tests: 4// 1. Empty sketch returns 0 5// 2. Insert <= k unique keys: every estimate is exact (error=0) 6// 3. Evict-when-full: insert k+1th key; smallest-count entry 7// gets replaced; new entry has count = min_count + 1, error 8// = min_count 9// 4. Heavy hitters (frequency > N/k) are NEVER evicted 10// 5. top_k returns descending-count order 11// 6. Lower bound: count - error <= true count 12// 7. Typed envelope: NX_ENV_ABS / conf 1.0 / max_overcount 13 14import "syscalls.nx" 15import "sketch_space_saving.nx" 16import "sketch_types.nx" 17 18func main() -> i64 { 19 let s: *SpaceSaving = nx_ss_alloc(5) 20 if s == (0 as *SpaceSaving) { return __syscall(93, 5, 0, 0, 0, 0, 0) } 21 22 // ---- empty ---- 23 if nx_ss_estimate(s, 42) != 0 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 24 if s.n_tracked != 0 { return __syscall(93, 11, 0, 0, 0, 0, 0) } 25 26 // ---- under-cap inserts ---- 27 nx_ss_add(s, 100, 1) 28 nx_ss_add(s, 200, 1) 29 nx_ss_add(s, 300, 1) 30 if s.n_tracked != 3 { return __syscall(93, 20, 0, 0, 0, 0, 0) } 31 if nx_ss_estimate(s, 100) != 1 { return __syscall(93, 21, 0, 0, 0, 0, 0) } 32 if nx_ss_error(s, 100) != 0 { return __syscall(93, 22, 0, 0, 0, 0, 0) } 33 // re-add 100: should increment, not duplicate 34 nx_ss_add(s, 100, 4) 35 if s.n_tracked != 3 { return __syscall(93, 23, 0, 0, 0, 0, 0) } 36 if nx_ss_estimate(s, 100) != 5 { return __syscall(93, 24, 0, 0, 0, 0, 0) } 37 38 // Fill to capacity: at k=5, add two more. 39 nx_ss_add(s, 400, 1) 40 nx_ss_add(s, 500, 1) 41 if s.n_tracked != 5 { return __syscall(93, 30, 0, 0, 0, 0, 0) } 42 // Current state: 100=5, 200=1, 300=1, 400=1, 500=1; total=9 43 44 // ---- evict-when-full ---- 45 // Adding new key 600 must evict the minimum. Min count = 1 46 // (tied across 200/300/400/500); find_min picks the first 47 // (lowest index = 1, which is key 200). New entry: key=600, 48 // count = 1 + 1 = 2, error = 1. 49 nx_ss_add(s, 600, 1) 50 if s.n_tracked != 5 { return __syscall(93, 40, 0, 0, 0, 0, 0) } 51 // 200 should be gone. 52 if nx_ss_estimate(s, 200) != 0 { return __syscall(93, 41, 0, 0, 0, 0, 0) } 53 // 600 should be present with count=2, error=1. 54 if nx_ss_estimate(s, 600) != 2 { return __syscall(93, 42, 0, 0, 0, 0, 0) } 55 if nx_ss_error(s, 600) != 1 { return __syscall(93, 43, 0, 0, 0, 0, 0) } 56 // Lower bound for 600 is count - error = 1, true count IS 1. 57 if nx_ss_lower_bound(s, 600) != 1 { 58 return __syscall(93, 44, 0, 0, 0, 0, 0) 59 } 60 61 // ---- heavy hitter survives evictions ---- 62 let s2: *SpaceSaving = nx_ss_alloc(3) 63 // Insert key 999 with count 100 first (heavy hitter). 64 nx_ss_add(s2, 999, 100) 65 // Now flood with 10 unique singletons. 66 var i: i64 = 0 67 while i < 10 { 68 nx_ss_add(s2, 1000 + i, 1) 69 i = i + 1 70 } 71 // 999 must still be tracked (heavy hitter). 72 if nx_ss_estimate(s2, 999) == 0 { 73 return __syscall(93, 50, 0, 0, 0, 0, 0) 74 } 75 // Its estimate may have been inflated slightly by collisions, 76 // but its lower bound should be >= 99. 77 if nx_ss_lower_bound(s2, 999) < 99 { 78 return __syscall(93, 51, 0, 0, 0, 0, 0) 79 } 80 81 // ---- top_k ordering ---- 82 let s3: *SpaceSaving = nx_ss_alloc(5) 83 nx_ss_add(s3, 1, 100) 84 nx_ss_add(s3, 2, 50) 85 nx_ss_add(s3, 3, 25) 86 nx_ss_add(s3, 4, 75) 87 let keys_raw: *u8 = sys_mmap(4 * 8) 88 let counts_raw: *u8 = sys_mmap(4 * 8) 89 let keys: *i64 = keys_raw as *i64 90 let counts: *i64 = counts_raw as *i64 91 let n: i64 = nx_ss_top_k(s3, 4, keys, counts) 92 if n != 4 { return __syscall(93, 60, 0, 0, 0, 0, 0) } 93 if keys[0] != 1 { return __syscall(93, 61, 0, 0, 0, 0, 0) } 94 if counts[0] != 100 { return __syscall(93, 62, 0, 0, 0, 0, 0) } 95 if keys[1] != 4 { return __syscall(93, 63, 0, 0, 0, 0, 0) } 96 if counts[1] != 75 { return __syscall(93, 64, 0, 0, 0, 0, 0) } 97 if keys[2] != 2 { return __syscall(93, 65, 0, 0, 0, 0, 0) } 98 if keys[3] != 3 { return __syscall(93, 66, 0, 0, 0, 0, 0) } 99 100 // ---- typed envelope ---- 101 let q: *ApproxI64 = nx_ss_query(s, 100) 102 if q.envelope_kind != NX_ENV_ABS { 103 return __syscall(93, 70, 0, 0, 0, 0, 0) 104 } 105 if q.conf_ppb != 1000000000 { 106 return __syscall(93, 71, 0, 0, 0, 0, 0) 107 } 108 if q.maturity != NX_MATURITY_REFERENCE_IMPL { 109 return __syscall(93, 72, 0, 0, 0, 0, 0) 110 } 111 if q.adv_safety != NX_ADV_HONEST { 112 return __syscall(93, 73, 0, 0, 0, 0, 0) 113 } 114 // max_overcount for s (k=5, total=10) = ceil(10/5) = 2. 115 if q.param_a != 2 { 116 return __syscall(93, 74, 0, 0, 0, 0, 0) 117 } 118 119 return 0 120}