From 1fba1e3419ad0c0e9fad7990e933ca6f8251f631 Mon Sep 17 00:00:00 2001 From: Kevin Stenerson Date: Thu, 6 Aug 2026 09:03:25 -0600 Subject: [PATCH 01/10] perf(v): render edigits output by divide and conquer --- bench/algorithm/edigits/1.v | 49 ++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/bench/algorithm/edigits/1.v b/bench/algorithm/edigits/1.v index de5de7eef..086d6cf0c 100644 --- a/bench/algorithm/edigits/1.v +++ b/bench/algorithm/edigits/1.v @@ -3,11 +3,15 @@ module main import os import strconv import math.big +import strings import math const one = big.integer_from_int(1) const ten = big.integer_from_int(10) +// Digits per leaf of `write_dec`; 10^leaf_digits - 1 still fits in an u32. +const leaf_digits = 9 + fn main() { mut n := 27 if os.args.len > 1 { @@ -17,20 +21,57 @@ fn main() { k := binary_search(n) mut p, q := sum_terms(0, k - 1) p += q - mut a := ten.pow(u32(n - 1)) + a := ten.pow(u32(n - 1)) answer := p * a / q - s := answer.str() + s := decimal_str(answer, n) + mut sb := strings.new_builder(n / 10 * 24) for i := 0; i < n; i += 10 { if i + 10 <= n { - println('${s[i..i + 10]}\t:${i + 10}') + sb.writeln('${s[i..i + 10]}\t:${i + 10}') } else { mut line := s[i..n] for _ in 0 .. (10 - n % 10) { line = '${line} ' } - print('${line}\t:${n}') + sb.write_string('${line}\t:${n}') + } + } + print(sb.str()) +} + +// Renders `x`, which is known to have exactly `n` decimal digits, by +// recursively halving it against precomputed powers of ten. +fn decimal_str(x big.Integer, n int) string { + mut pows := [ten.pow(leaf_digits)] + mut width := leaf_digits + for width < n { + pows << pows.last() * pows.last() + width *= 2 + } + mut buf := []u8{len: width} + write_dec(x, pows.len - 1, pows, mut buf) + return buf[width - n..].bytestr() +} + +fn write_dec(x big.Integer, level int, pows []big.Integer, mut buf []u8) { + if x.signum == 0 { + for i in 0 .. buf.len { + buf[i] = `0` + } + return + } + if level == 0 { + mut v := u32(x.int()) + for i := leaf_digits - 1; i >= 0; i-- { + buf[i] = u8(`0` + v % 10) + v /= 10 } + return } + hi, lo := x.div_mod(pows[level - 1]) + half := leaf_digits << (level - 1) + write_dec(hi, level - 1, pows, mut buf[..half]) + write_dec(lo, level - 1, pows, mut buf[half..]) } fn sum_terms(a int, b int) (big.Integer, big.Integer) { From 0075fb3a70cac410782e2c200728122b7048c708 Mon Sep 17 00:00:00 2001 From: Kevin Stenerson Date: Thu, 6 Aug 2026 09:03:25 -0600 Subject: [PATCH 02/10] perf(v): back the lru cache with a node array and hash chains --- bench/algorithm/lru/1.v | 123 ++++++++++++++++++++++++++++++++++------ 1 file changed, 105 insertions(+), 18 deletions(-) diff --git a/bench/algorithm/lru/1.v b/bench/algorithm/lru/1.v index 4f0b49160..85444aadb 100644 --- a/bench/algorithm/lru/1.v +++ b/bench/algorithm/lru/1.v @@ -17,29 +17,119 @@ fn (mut lcg LCG) next() u32 { return lcg.seed } +struct Node { +mut: + key u32 + value u32 + prev int // recency list, towards least recently used + next int // recency list, towards most recently used + hnext int // next entry in this node's hash bucket +} + +// Slot 0 is a sentinel the recency list runs circularly through, and it also +// terminates the hash chains, so neither needs a nil check. struct LRU { - size int + size int + shift u32 mut: - m map[u32]u32 + nodes []Node + buckets []int +} + +fn new_lru(size int) LRU { + mut bits := u32(1) + for (1 << bits) < size * 2 { + bits++ + } + mut nodes := []Node{cap: size + 1} + nodes << Node{} + return LRU{ + size: size + shift: 32 - bits + nodes: nodes + buckets: []int{len: 1 << bits} + } +} + +// Fibonacci hashing: the high bits of the product are the well mixed ones. +@[inline] +fn (lru &LRU) bucket_of(key u32) int { + return int((key * u32(2654435761)) >> lru.shift) +} + +@[direct_array_access] +fn (lru &LRU) find(key u32) int { + mut i := lru.buckets[lru.bucket_of(key)] + for i != 0 && lru.nodes[i].key != key { + i = lru.nodes[i].hnext + } + return i } -fn (mut lru LRU) get(key u32) !u32 { - v := lru.m[key] or { return error('not found') } - lru.m.delete(key) - lru.m[key] = v - return v +@[inline] +fn (mut lru LRU) hash_insert(i int, key u32) { + b := lru.bucket_of(key) + lru.nodes[i].hnext = lru.buckets[b] + lru.buckets[b] = i } +fn (mut lru LRU) hash_remove(key u32) { + b := lru.bucket_of(key) + mut i := lru.buckets[b] + if lru.nodes[i].key == key { + lru.buckets[b] = lru.nodes[i].hnext + return + } + for lru.nodes[lru.nodes[i].hnext].key != key { + i = lru.nodes[i].hnext + } + lru.nodes[i].hnext = lru.nodes[lru.nodes[i].hnext].hnext +} + +@[direct_array_access; inline] +fn (mut lru LRU) move_to_back(i int) { + prev := lru.nodes[i].prev + next := lru.nodes[i].next + lru.nodes[prev].next = next + lru.nodes[next].prev = prev + tail := lru.nodes[0].prev + lru.nodes[i].prev = tail + lru.nodes[i].next = 0 + lru.nodes[tail].next = i + lru.nodes[0].prev = i +} + +@[direct_array_access] +fn (mut lru LRU) get(key u32) ?u32 { + i := lru.find(key) + if i == 0 { + return none + } + lru.move_to_back(i) + return lru.nodes[i].value +} + +@[direct_array_access] fn (mut lru LRU) put(key u32, value u32) { - if key in lru.m { - lru.m.delete(key) - } else if lru.m.len == lru.size { - for k, _ in lru.m { - lru.m.delete(k) - break + mut i := lru.find(key) + if i == 0 { + if lru.nodes.len > lru.size { + i = lru.nodes[0].next + lru.hash_remove(lru.nodes[i].key) + lru.nodes[i].key = key + } else { + i = lru.nodes.len + // Self-linked, so the unlink inside move_to_back is a no-op. + lru.nodes << Node{ + key: key + prev: i + next: i + } } + lru.hash_insert(i, key) } - lru.m[key] = value + lru.nodes[i].value = value + lru.move_to_back(i) } fn main() { @@ -59,10 +149,7 @@ fn main() { mut rng1 := LCG{ seed: 1 } - mut lru := LRU{ - size: size - m: {} - } + mut lru := new_lru(size) mut hit := 0 mut missed := 0 From aaf658427f28a7117e4d2354ce313ba9739a0cd7 Mon Sep 17 00:00:00 2001 From: Kevin Stenerson Date: Thu, 6 Aug 2026 09:03:25 -0600 Subject: [PATCH 03/10] perf(v): pick fasta bases from a per-rng-state table --- bench/algorithm/fasta/1.v | 194 +++++++++++++++++++++++--------------- 1 file changed, 116 insertions(+), 78 deletions(-) diff --git a/bench/algorithm/fasta/1.v b/bench/algorithm/fasta/1.v index c0f835937..9177a36ce 100644 --- a/bench/algorithm/fasta/1.v +++ b/bench/algorithm/fasta/1.v @@ -2,7 +2,6 @@ module main import os import strconv -import strings const line_width = 60 @@ -12,8 +11,46 @@ const ia = 3877 const ic = 29573 +const buf_size = 1 << 16 + const alu = 'GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAATACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCCAGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAA'.bytes() +struct AminoAcid { + c u8 +mut: + p f64 +} + +// Buffers output, flushing to the OS in 64 KiB blocks. +struct Out { +mut: + f os.File = os.stdout() + buf []u8 = []u8{len: buf_size} + pos int +} + +@[inline] +fn (mut o Out) put(c u8) { + o.buf[o.pos] = c + o.pos++ + if o.pos == buf_size { + o.flush() + } +} + +fn (mut o Out) put_string(s string) { + for c in s { + o.put(c) + } +} + +fn (mut o Out) flush() { + if o.pos > 0 { + o.f.write(o.buf[..o.pos]) or {} + o.pos = 0 + } +} + fn main() { mut n := 1 if os.args.len == 2 { @@ -24,96 +61,96 @@ fn main() { seed: 42 } - mut iub := { - byte(`a`): 0.27 - byte(`c`): 0.12 - byte(`g`): 0.12 - byte(`t`): 0.27 - byte(`B`): 0.02 - byte(`D`): 0.02 - byte(`H`): 0.02 - byte(`K`): 0.02 - byte(`M`): 0.02 - byte(`N`): 0.02 - byte(`R`): 0.02 - byte(`S`): 0.02 - byte(`V`): 0.02 - byte(`W`): 0.02 - byte(`Y`): 0.02 - } + mut iub := [ + AminoAcid{`a`, 0.27}, + AminoAcid{`c`, 0.12}, + AminoAcid{`g`, 0.12}, + AminoAcid{`t`, 0.27}, + AminoAcid{`B`, 0.02}, + AminoAcid{`D`, 0.02}, + AminoAcid{`H`, 0.02}, + AminoAcid{`K`, 0.02}, + AminoAcid{`M`, 0.02}, + AminoAcid{`N`, 0.02}, + AminoAcid{`R`, 0.02}, + AminoAcid{`S`, 0.02}, + AminoAcid{`V`, 0.02}, + AminoAcid{`W`, 0.02}, + AminoAcid{`Y`, 0.02}, + ] + + mut homosapiens := [ + AminoAcid{`a`, 0.3029549426680}, + AminoAcid{`c`, 0.1979883004921}, + AminoAcid{`g`, 0.1975473066391}, + AminoAcid{`t`, 0.3015094502008}, + ] + + accumulate(mut iub) + accumulate(mut homosapiens) + + mut o := Out{} + o.put_string('>ONE Homo sapiens alu\n') + make_repeat_fasta(mut o, alu, n * 2) + o.put_string('>TWO IUB ambiguity codes\n') + make_random_fasta(mut o, mut rand, iub, n * 3) + o.put_string('>THREE Homo sapiens frequency\n') + make_random_fasta(mut o, mut rand, homosapiens, n * 5) + o.flush() +} - mut homosapiens := { - byte(`a`): 0.3029549426680 - byte(`c`): 0.1979883004921 - byte(`g`): 0.1975473066391 - byte(`t`): 0.3015094502008 +fn accumulate(mut table []AminoAcid) { + for i in 1 .. table.len { + table[i].p += table[i - 1].p } - - make_repeat_fasta('ONE', 'Homo sapiens alu', alu, n * 2) - make_random_fasta(mut rand, 'TWO', 'IUB ambiguity codes', mut iub, n * 3) - make_random_fasta(mut rand, 'THREE', 'Homo sapiens frequency', mut homosapiens, n * 5) } -fn make_repeat_fasta(id string, desc string, src []byte, n int) { - println('>${id} ${desc}') - mut char_print_idx := 0 - mut sb := strings.new_builder(line_width) - unsafe { - defer { - sb.free() +// `src` is repeated once so any 60-byte window can be copied without wrapping. +fn make_repeat_fasta(mut o Out, src []u8, n int) { + wrapped := []u8{len: src.len + line_width, init: src[index % src.len]} + mut pos := 0 + mut left := n + for left > 0 { + line := if left < line_width { left } else { line_width } + for i in 0 .. line { + o.put(wrapped[pos + i]) } - } - for _ in 0 .. (n / src.len + 1) { - for c in src { - sb.write_byte(c) - char_print_idx += 1 - if char_print_idx >= n { - if char_print_idx % line_width != 0 { - println(sb.str()) - } - break - } - - if char_print_idx % line_width == 0 { - println(sb.str()) - sb.go_back_to(0) - } + o.put(`\n`) + pos += line + if pos >= src.len { + pos -= src.len } + left -= line } } -fn make_random_fasta(mut rand_gen RandGen, id string, desc string, mut table map[byte]f64, n int) { - println('>${id} ${desc}') - mut prob := 0.0 - for k, p in table { - prob += p - table[k] = prob - } - - mut n_char_printed := 0 - mut sb := strings.new_builder(line_width) - unsafe { - defer { - sb.free() +// The generator has only `im` states and the cumulative probabilities are +// increasing, so resolve every state's pick up front in one monotone sweep. +fn make_random_fasta(mut o Out, mut rand_gen RandGen, table []AminoAcid, n int) { + mut lut := []u8{len: im} + mut k := 0 + for s in 0 .. im { + rand := f64(s) / f64(im) + for k < table.len && table[k].p <= rand { + k++ } + lut[s] = if k < table.len { table[k].c } else { 0 } } + + mut col := 0 for _ in 0 .. n { - rand := rand_gen.gen_random() - for k, p in table { - if p > rand { - sb.write_byte(k) - n_char_printed += 1 - if n_char_printed % line_width == 0 { - println(sb.str()) - sb.go_back_to(0) - } - break + c := lut[rand_gen.next()] + if c != 0 { + o.put(c) + col++ + if col == line_width { + o.put(`\n`) + col = 0 } } } - - if n_char_printed % line_width != 0 { - println(sb.str()) + if col != 0 { + o.put(`\n`) } } @@ -122,7 +159,8 @@ mut: seed int } -fn (mut r RandGen) gen_random() f64 { +@[inline] +fn (mut r RandGen) next() int { r.seed = (r.seed * ia + ic) % im - return f64(r.seed) / f64(im) + return r.seed } From bbd96ff1ba092e18492dfd8f646f34ea6731cf2b Mon Sep 17 00:00:00 2001 From: Kevin Stenerson Date: Thu, 6 Aug 2026 09:03:26 -0600 Subject: [PATCH 04/10] perf(v): halve the big divisions per pidigits digit --- bench/algorithm/pidigits/2.v | 50 +++++++++++++----------------------- 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/bench/algorithm/pidigits/2.v b/bench/algorithm/pidigits/2.v index 51b74780b..872cd9579 100644 --- a/bench/algorithm/pidigits/2.v +++ b/bench/algorithm/pidigits/2.v @@ -5,7 +5,6 @@ import strconv import math.big import strings -const zero = big.integer_from_int(0) const one = big.integer_from_int(1) const two = big.integer_from_int(2) const three = big.integer_from_int(3) @@ -20,60 +19,47 @@ fn main() { digits_to_print := n mut digits_printed := 0 - mut k := one + mut k := 1 mut n1 := four mut n2 := three mut d := one - mut u := zero - mut v := zero - mut w := zero - mut sb := strings.new_builder(12 + n.str().len) - unsafe { - defer { - sb.free() - } - } + mut sb := strings.new_builder(digits_to_print / 10 * 24 + 32) for { - u = n1 / d - v = n2 / d - u_int := u.int() + // `n1 > n2` holds throughout, so floor(n1/d) and floor(n2/d) agree + // exactly when `n1 < (floor(n2/d) + 1) * d`. + v := n2 / d v_int := v.int() + upper := (v + one) * d - if u_int == v_int { - sb.write_byte(u_int.str()[0]) + if n1 < upper { + sb.write_u8(u8(`0` + v_int)) digits_printed++ digits_printed_mod_ten := digits_printed % 10 if digits_printed_mod_ten == 0 { - sb.write_string('\t:${digits_printed}') - println(sb.str()) - sb.go_back_to(0) + sb.write_string('\t:${digits_printed}\n') } if digits_printed >= digits_to_print { if digits_printed_mod_ten > 0 { for _ in 0 .. (10 - digits_printed_mod_ten) { - sb.write_byte(` `) + sb.write_u8(` `) } - sb.write_string('\t:${digits_printed}') - println(sb.str()) + sb.write_string('\t:${digits_printed}\n') } + print(sb.str()) return } - to_minus := u * ten * d + to_minus := v * ten * d n1 = n1 * ten - to_minus n2 = n2 * ten - to_minus } else { - k2 := k * two - u = n1 * (k2 - one) - v = n2 * two - w = n1 * (k - one) - n1 = u + v - u = n2 * (k + two) - n2 = w + u - d = d * (k2 + one) - k += one + prev_n1 := n1 + n1 = n1 * big.integer_from_int(2 * k - 1) + n2 * two + n2 = prev_n1 * big.integer_from_int(k - 1) + n2 * big.integer_from_int(k + 2) + d = d * big.integer_from_int(2 * k + 1) + k++ } } } From e8d3a1c6a50fa33586310da4c06806ece58f65cb Mon Sep 17 00:00:00 2001 From: Kevin Stenerson Date: Thu, 6 Aug 2026 09:03:26 -0600 Subject: [PATCH 05/10] perf(v): carry the spectral-norm divisor as a running sum --- bench/algorithm/spectral-norm/1.v | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/bench/algorithm/spectral-norm/1.v b/bench/algorithm/spectral-norm/1.v index 6ac68c135..df59a363d 100644 --- a/bench/algorithm/spectral-norm/1.v +++ b/bench/algorithm/spectral-norm/1.v @@ -22,8 +22,13 @@ fn evala(i int, j int) int { fn times(mut v []f64, u []f64) { for i in 0 .. v.len { mut a := f64(0) + // evala(i, j+1) - evala(i, j) is i+j+1, itself growing by one per column + mut e := f64(evala(i, 0)) + mut step := f64(i + 1) for j in 0 .. u.len { - a += u[j] / f64(evala(i, j)) + a += u[j] / e + e += step + step += 1.0 } v[i] = a } @@ -33,8 +38,12 @@ fn times(mut v []f64, u []f64) { fn times_trans(mut v []f64, u []f64) { for i in 0 .. v.len { mut a := f64(0) + mut e := f64(evala(0, i)) + mut step := f64(i + 2) for j in 0 .. u.len { - a += u[j] / f64(evala(j, i)) + a += u[j] / e + e += step + step += 1.0 } v[i] = a } From 9fa63cb1b77d3602e251a1ed66f138dc3a4bc0b0 Mon Sep 17 00:00:00 2001 From: Kevin Stenerson Date: Thu, 6 Aug 2026 09:03:26 -0600 Subject: [PATCH 06/10] perf(v): allocate binarytrees nodes from a per-depth arena --- bench/algorithm/binarytrees/1.v | 60 +++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/bench/algorithm/binarytrees/1.v b/bench/algorithm/binarytrees/1.v index 980e05cbf..b5c4e970e 100644 --- a/bench/algorithm/binarytrees/1.v +++ b/bench/algorithm/binarytrees/1.v @@ -2,34 +2,48 @@ import strconv import os struct Node { - left &Node - right &Node + left &Node = unsafe { nil } + right &Node = unsafe { nil } } fn check(node &Node) int { mut ret := 1 - unsafe { - if node.left != 0 { - ret += check(node.left) - } - if node.right != 0 { - ret += check(node.right) - } + if node.left != nil { + ret += check(node.left) + } + if node.right != nil { + ret += check(node.right) } return ret } -fn create(n int) &Node { - if n == 0 { - return &Node{ - left: unsafe { nil } - right: unsafe { nil } - } +// Nodes are carved out of one block per tree; the block is reset and reused +// between trees of the same depth. +struct Arena { +mut: + nodes []Node + pos int +} + +fn new_arena(depth int) Arena { + return Arena{ + nodes: []Node{len: (1 << (depth + 1)) - 1} } - return &Node{ - left: create(n - 1) - right: create(n - 1) +} + +@[direct_array_access] +fn (mut a Arena) create(depth int) &Node { + i := a.pos + a.pos++ + a.nodes[i] = if depth == 0 { + Node{} + } else { + Node{ + left: a.create(depth - 1) + right: a.create(depth - 1) + } } + return unsafe { &a.nodes[i] } } const min_depth = 4 @@ -41,10 +55,12 @@ fn main() { } stretch_depth := max_depth + 1 - stretch_tree := create(stretch_depth) + mut stretch_arena := new_arena(stretch_depth) + stretch_tree := stretch_arena.create(stretch_depth) println('stretch tree of depth ${stretch_depth}\t check: ${check(stretch_tree)}') - long_lived_tree := create(max_depth) + mut long_lived_arena := new_arena(max_depth) + long_lived_tree := long_lived_arena.create(max_depth) n_results := (max_depth - min_depth) / 2 + 1 @@ -52,9 +68,11 @@ fn main() { depth := i * 2 + min_depth n := 1 << (max_depth - depth + min_depth) + mut arena := new_arena(depth) mut check_result := 0 for _ in 0 .. n { - node := create(depth) + arena.pos = 0 + node := arena.create(depth) check_result += check(node) } From f6a3cf23754f3a4ffdf2d9a104c723f8a814cf15 Mon Sep 17 00:00:00 2001 From: Kevin Stenerson Date: Thu, 6 Aug 2026 09:03:26 -0600 Subject: [PATCH 07/10] feat(v): enable the mandelbrot benchmark --- bench/bench_v.yaml | 2 +- bench/bench_v_autofree.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/bench_v.yaml b/bench/bench_v.yaml index 5dab1482e..8389c1952 100644 --- a/bench/bench_v.yaml +++ b/bench/bench_v.yaml @@ -33,7 +33,7 @@ problems: # - 1.v - name: mandelbrot source: - # - 1.v + - 1.v - name: nsieve source: - 1.v diff --git a/bench/bench_v_autofree.yaml b/bench/bench_v_autofree.yaml index c76ecb0da..861eea405 100644 --- a/bench/bench_v_autofree.yaml +++ b/bench/bench_v_autofree.yaml @@ -33,7 +33,7 @@ problems: # - 1.v - name: mandelbrot source: - # - 1.v + - 1.v - name: nsieve source: - 1.v From cf6cccbb439e6bb0811d5bfb4597c2e32f46e872 Mon Sep 17 00:00:00 2001 From: Kevin Stenerson Date: Thu, 6 Aug 2026 09:03:26 -0600 Subject: [PATCH 08/10] feat(v): enable json-serde using json2 --- bench/algorithm/json-serde/1.v | 6 +++--- bench/bench_v.yaml | 2 +- bench/bench_v_autofree.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bench/algorithm/json-serde/1.v b/bench/algorithm/json-serde/1.v index 34a2a1378..6103ede86 100644 --- a/bench/algorithm/json-serde/1.v +++ b/bench/algorithm/json-serde/1.v @@ -2,7 +2,7 @@ module main import os import strconv -import json +import json2 as json import crypto.md5 fn main() { @@ -12,9 +12,9 @@ fn main() { n = strconv.atoi(os.args[2]) or { 3 } } json_str := os.read_file('${file_name}.json')! - data := json.decode(GeoData, json_str)! + data := json.decode[GeoData](json_str)! println(md5.hexhash(json.encode(data))) - array := []GeoData{len: n, cap: n, init: json.decode(GeoData, json_str)!} + array := []GeoData{len: n, cap: n, init: json.decode[GeoData](json_str)!} println(md5.hexhash(json.encode(array))) } diff --git a/bench/bench_v.yaml b/bench/bench_v.yaml index 8389c1952..87676b638 100644 --- a/bench/bench_v.yaml +++ b/bench/bench_v.yaml @@ -30,7 +30,7 @@ problems: - 1.v - name: json-serde source: - # - 1.v + - 1.v - name: mandelbrot source: - 1.v diff --git a/bench/bench_v_autofree.yaml b/bench/bench_v_autofree.yaml index 861eea405..837149bfc 100644 --- a/bench/bench_v_autofree.yaml +++ b/bench/bench_v_autofree.yaml @@ -30,7 +30,7 @@ problems: - 1.v - name: json-serde source: - # - 1.v + - 1.v - name: mandelbrot source: - 1.v From 0ece40477594b21524a23379458af96dfb7ca7b8 Mon Sep 17 00:00:00 2001 From: Kevin Stenerson Date: Thu, 6 Aug 2026 09:03:27 -0600 Subject: [PATCH 09/10] feat(v): add a parallel fannkuch-redux variant --- bench/algorithm/fannkuch-redux/1-m.v | 142 +++++++++++++++++++++++++++ bench/bench_v.yaml | 1 + bench/bench_v_autofree.yaml | 1 + 3 files changed, 144 insertions(+) create mode 100644 bench/algorithm/fannkuch-redux/1-m.v diff --git a/bench/algorithm/fannkuch-redux/1-m.v b/bench/algorithm/fannkuch-redux/1-m.v new file mode 100644 index 000000000..1777d960f --- /dev/null +++ b/bench/algorithm/fannkuch-redux/1-m.v @@ -0,0 +1,142 @@ +module main + +import os +import runtime +import strconv + +const max_n = 16 + +struct Result { + checksum int + max_flips int +} + +fn main() { + mut n := 10 + if os.args.len == 2 { + n = strconv.atoi(os.args[1]) or { 10 } + } + + total := factorials(n)[n] + + mut nthreads := runtime.nr_cpus() + if nthreads < 1 { + nthreads = 1 + } + // Chunks must start on an even index: the checksum's sign derives from it. + nchunks := i64(nthreads) * 4 + mut chunk := (total + nchunks - 1) / nchunks + chunk += chunk % 2 + + mut workers := []thread Result{} + for lo := i64(0); lo < total; lo += chunk { + hi := if lo + chunk < total { lo + chunk } else { total } + workers << spawn fannkuchredux(n, lo, hi) + } + results := workers.wait() + + mut checksum := 0 + mut max_flips := 0 + for r in results { + checksum += r.checksum + if r.max_flips > max_flips { + max_flips = r.max_flips + } + } + + println('${checksum}\nPfannkuchen(${n}) = ${max_flips}') +} + +fn factorials(n int) []i64 { + mut fact := []i64{len: n + 1} + fact[0] = 1 + for i in 1 .. n + 1 { + fact[i] = fact[i - 1] * i + } + return fact +} + +// Walks permutations [idx_min, idx_max), seeking to the start once and then +// advancing incrementally, and skipping the flip count when p[0] is 0. +fn fannkuchredux(n int, idx_min i64, idx_max i64) Result { + fact := factorials(n) + mut p := [max_n]int{} + mut pp := [max_n]int{} + mut count := [max_n]int{} + + for i in 0 .. n { + p[i] = i + } + mut seek := idx_min + for i := n - 1; i > 0; i-- { + d := int(seek / fact[i]) + count[i] = d + seek %= fact[i] + pp = p + for j in 0 .. i + 1 { + p[j] = if j + d <= i { pp[j + d] } else { pp[j + d - i - 1] } + } + } + + mut max_flips := 1 + mut checksum := 0 + mut sign := true + + for idx := idx_min; true; idx++ { + first := p[0] + if first != 0 { + mut flips := 1 + if p[first] != 0 { + pp = p + mut p0 := first + for { + flips++ + for i, j := 1, p0 - 1; i < j; i, j = i + 1, j - 1 { + pp[i], pp[j] = pp[j], pp[i] + } + t := pp[p0] + pp[p0] = p0 + p0 = t + if pp[p0] == 0 { + break + } + } + } + if max_flips < flips { + max_flips = flips + } + if sign { + checksum += flips + } else { + checksum -= flips + } + } + + if idx + 1 == idx_max { + break + } + + if sign { + p[0], p[1] = p[1], first + sign = false + } else { + p[1], p[2] = p[2], p[1] + sign = true + mut f := first + for k := 2; true; k++ { + count[k]++ + if count[k] <= k { + break + } + count[k] = 0 + for j in 0 .. k + 1 { + p[j] = p[j + 1] + } + p[k + 1] = f + f = p[0] + } + } + } + + return Result{checksum, max_flips} +} diff --git a/bench/bench_v.yaml b/bench/bench_v.yaml index 87676b638..691888113 100644 --- a/bench/bench_v.yaml +++ b/bench/bench_v.yaml @@ -25,6 +25,7 @@ problems: - name: fannkuch-redux source: - 1.v + - 1-m.v - name: coro-prime-sieve source: - 1.v diff --git a/bench/bench_v_autofree.yaml b/bench/bench_v_autofree.yaml index 837149bfc..12ab0bdb6 100644 --- a/bench/bench_v_autofree.yaml +++ b/bench/bench_v_autofree.yaml @@ -25,6 +25,7 @@ problems: - name: fannkuch-redux source: - 1.v + - 1-m.v - name: coro-prime-sieve source: - 1.v From 0931f39439e1dbf65eda40b3d998787f8964192b Mon Sep 17 00:00:00 2001 From: Kevin Stenerson Date: Thu, 6 Aug 2026 09:03:27 -0600 Subject: [PATCH 10/10] feat(v): add a parallel regex-redux variant --- bench/algorithm/regex-redux/1-m.v | 58 +++++++++++++++++++++++++++++++ bench/bench_v.yaml | 1 + 2 files changed, 59 insertions(+) create mode 100644 bench/algorithm/regex-redux/1-m.v diff --git a/bench/algorithm/regex-redux/1-m.v b/bench/algorithm/regex-redux/1-m.v new file mode 100644 index 000000000..3fe1c0720 --- /dev/null +++ b/bench/algorithm/regex-redux/1-m.v @@ -0,0 +1,58 @@ +module main + +import os +import regex + +fn main() { + file_name := os.args[1] or { '25000_in' } + mut content := os.read_file(file_name)! + ilen := content.len + mut replace_re := regex.regex_opt('(>.*\n)|(\n)')! + content = replace_re.replace(content, '') + clen := content.len + + patterns := [ + 'agggtaaa|tttaccct', + '[cgt]gggtaaa|tttaccc[acg]', + 'a[act]ggtaaa|tttacc[agt]t', + 'ag[act]gtaaa|tttac[agt]ct', + 'agg[act]taaa|ttta[agt]cct', + 'aggg[acg]aaa|ttt[cgt]ccct', + 'agggt[cgt]aa|tt[acg]accct', + 'agggta[cgt]a|t[acg]taccct', + 'agggtaa[cgt]|[acg]ttaccct', + ] + + // The counts are independent scans of the same text; the substitutions + // below are not, as each consumes the previous one's output. + mut counts := []thread int{cap: patterns.len} + for p in patterns { + counts << spawn var_find(content, p) + } + for i, n in counts.wait() { + println('${patterns[i]} ${n}') + } + + for p, r in { + 'tHa[Nt]': '<4>' + '(aND)|(caN)|(Ha[DS])|(WaS)': '<3>' + '(a[NSt])|(BY)': '<2>' + '<[^>]*>': '|' + '\\|[^|][^|]*\\|': '-' + } { + mut re := regex.regex_opt(p)! + content = re.replace(content, r) + } + println('\n${ilen}\n${clen}\n${content.len}') +} + +fn var_find(content string, pattern string) int { + mut re := regex.regex_opt(normalize_pattern(pattern)) or { panic(err) } + matches := re.find_all(content) + return matches.len / 2 +} + +// vlib's regex matches nothing for a bare top-level `a|b`. +fn normalize_pattern(pattern string) string { + return pattern.split('|').map('(${it})').join('|') +} diff --git a/bench/bench_v.yaml b/bench/bench_v.yaml index 691888113..d7f85d98e 100644 --- a/bench/bench_v.yaml +++ b/bench/bench_v.yaml @@ -48,6 +48,7 @@ problems: - name: regex-redux source: - 1.v + - 1-m.v compiler_version_command: v --version compiler_version_regex: runtime_version_parameter: