From a1c6377ab688e4ce40956675c27ef12247d04721 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 16 Mar 2024 16:32:47 +0200 Subject: [PATCH] rand: add PRNG.fill_buffer_from_set/2 (#21037) --- vlib/rand/rand.c.v | 16 +++- vlib/rand/rand.js.v | 4 + vlib/rand/rand.v | 12 +++ vlib/rand/random_numbers_test.v | 88 ++++++++++++------- .../bench/bench_rand_fill_buffer_from_set.v | 54 ++++++++++++ 5 files changed, 143 insertions(+), 31 deletions(-) create mode 100644 vlib/v/tests/bench/bench_rand_fill_buffer_from_set.v diff --git a/vlib/rand/rand.c.v b/vlib/rand/rand.c.v index 0ca2c6c3d..5b1cf47e9 100644 --- a/vlib/rand/rand.c.v +++ b/vlib/rand/rand.c.v @@ -93,6 +93,7 @@ fn internal_ulid_at_millisecond(mut rng PRNG, unix_time_milli u64) string { } } +@[direct_array_access] fn internal_string_from_set(mut rng PRNG, charset string, len int) string { if len == 0 { return '' @@ -100,7 +101,7 @@ fn internal_string_from_set(mut rng PRNG, charset string, len int) string { mut buf := unsafe { malloc_noscan(len + 1) } for i in 0 .. len { unsafe { - buf[i] = charset[intn(charset.len) or { 0 }] + buf[i] = charset[rng.u32() % charset.len] } } unsafe { @@ -109,6 +110,19 @@ fn internal_string_from_set(mut rng PRNG, charset string, len int) string { return unsafe { buf.vstring_with_len(len) } } +@[direct_array_access] +fn internal_fill_buffer_from_set(mut rng PRNG, charset string, mut buf []u8) { + if buf.len == 0 { + return + } + blen := buf.len + for i in 0 .. blen { + unsafe { + buf[i] = charset[rng.u32() % charset.len] + } + } +} + fn deinit() { unsafe { default_rng.free() // free the implementation diff --git a/vlib/rand/rand.js.v b/vlib/rand/rand.js.v index 9c33c7f1e..f06bb4c18 100644 --- a/vlib/rand/rand.js.v +++ b/vlib/rand/rand.js.v @@ -5,6 +5,10 @@ fn init() { default_rng = new_default() } +fn internal_fill_buffer_from_set(mut rng PRNG, charset string, mut buf []u8) { + panic('todo') +} + fn internal_string_from_set(mut rng PRNG, charset string, len int) string { result := '' # diff --git a/vlib/rand/rand.v b/vlib/rand/rand.v index b651d85c8..f0913a65a 100644 --- a/vlib/rand/rand.v +++ b/vlib/rand/rand.v @@ -351,6 +351,12 @@ pub fn (mut rng PRNG) ascii(len int) string { return internal_string_from_set(mut rng, rand.ascii_chars, len) } +// fill_buffer_from_set fills the mutable `buf` with random characters from the given `charset` +@[inline] +pub fn (mut rng PRNG) fill_buffer_from_set(charset string, mut buf []u8) { + internal_fill_buffer_from_set(mut rng, charset, mut buf) +} + // bernoulli returns true with a probability p. Note that 0 <= p <= 1. pub fn (mut rng PRNG) bernoulli(p f64) !bool { if p < 0 || p > 1 { @@ -675,6 +681,12 @@ pub fn string_from_set(charset string, len int) string { return default_rng.string_from_set(charset, len) } +// fill_buffer_from_set fills the array `buf` with random characters sampled from the given `charset` +@[inline] +pub fn fill_buffer_from_set(charset string, mut buf []u8) { + default_rng.fill_buffer_from_set(charset, mut buf) +} + // string returns a string of length `len` containing random characters in range `[a-zA-Z]`. pub fn string(len int) string { return string_from_set(rand.english_letters, len) diff --git a/vlib/rand/random_numbers_test.v b/vlib/rand/random_numbers_test.v index ad7efa68e..3cca1916b 100644 --- a/vlib/rand/random_numbers_test.v +++ b/vlib/rand/random_numbers_test.v @@ -236,19 +236,44 @@ fn test_rand_string_from_set() { } } +fn test_rand_fill_buffer_from_set() { + rand.seed([u32(0), 1]) + outputs := [ + [u8(52), 48, 55, 57, 50, 49, 53, 49, 53, 53], + [u8(57), 51, 56, 53, 56, 55, 56, 52, 56, 51], + [u8(57), 54, 52, 53, 57, 56, 57, 57, 48, 57], + [u8(57), 54, 50, 50, 52, 57, 53, 55, 50, 57], + [u8(51), 48, 55, 54, 49, 55, 53, 54, 52, 57], + [u8(57), 50, 48, 50, 48, 49, 52, 54, 50, 48], + [u8(55), 54, 51, 48, 51, 54, 49, 55, 56, 52], + [u8(52), 56, 52, 54, 50, 50, 50, 56, 54, 53], + [u8(53), 53, 55, 52, 51, 54, 55, 56, 51, 51], + [u8(52), 50, 51, 57, 54, 52, 50, 48, 49, 53], + [u8(49), 51, 54, 57, 55, 51, 48, 51, 51, 50], + [u8(56), 54, 50, 54, 51, 54, 49, 55, 57, 49], + ] + for output in outputs { + mut buf := []u8{len: 10} + rand.fill_buffer_from_set('0123456789', mut buf) + assert buf == output + } +} + fn test_rand_string() { rand.seed([u32(0), 1]) outputs := [ - 'rzJfVBJgvAyCNpEdXIteDQezg', - 'AJOeswgoelDOCfcrSUWzVPjeL', - 'NQfKauQqsXYXSUMFPGnXXPJIn', - 'vfBGUKbpLoBMQVYXfkvRplWih', - 'aYHLjMJqvUJmJJHGxEnrEmQGl', - 'rBJXkQZcembAteaRFoxXmECJo', - 'HYVLfHmDOCTlSbiSzHrsAIaBH', - 'zgOiwyISjLSdLGhLzJsSKHVBi', - 'UiAtobWXGcHsEtgzuNatxfkoI', - 'NisnYlffJgFEcIdcgzWcGjnHy', + 'oIfPOHLBZTlvGhYtCMolfssbZ', + 'yHFGzDYeWIRldsBzMtkDhzQqF', + 'vwoeerAKsEZiludKtRKoCoiuE', + 'EQAaJDRZkvKTKNLkEPhWeEKFX', + 'rDIhxzIbDUIusiTuzLHRslfzu', + 'KCUoAEugYvUwzXcKRrAiwMzXH', + 'NIOXerfCpEwbfhLmbbWKjoxbL', + 'baJWQWarRRRmXCvMKcEjxQBpk', + 'CkVLxbJEPhviBTohEVBnMAFHZ', + 'ZdnGGhYShqzwnDXqHncLgLcdo', + 'zRiSLsgnApmvtlIVrQQaBzOJD', + 'VeeBcztImGquJnzEsXCdUaUed', ] for output in outputs { assert rand.string(25) == output @@ -258,16 +283,20 @@ fn test_rand_string() { fn test_rand_hex() { rand.seed([u32(0), 1]) outputs := [ - 'fc30e495deee09e008e15ffc3', - '4320efa837788397fb59b28f4', - '4995210abf33b6765c240ce62', - 'f3d20dbe0a8aa6b9c88cd1f6f', - '8d7d58b256ab00213dd519cf7', - 'fa2251284bc20a21eff48127c', - '5fef90cdc0c37143117599092', - '2a6170531c76dfb50c54126bc', - 'a686dfd536042d1c1a9afdaf4', - '7f12013f6e1177e2d63726de3', + '847b633d9f9765c1a84d38035', + 'efdef342641958db89cfdb4e1', + '704ee34204d29e9e99aca0ae0', + '0c8e1fd5472f65fc4b9668adf', + '3349538378c2023ef7f14dfbe', + 'ae4080a0cb4cbb0693c68037b', + '90e3a7be588b3dfeb3663c97f', + 'f25a82eb559ab6f0288bd8590', + '649f579cb93e9f414d9f40539', + '553a210a52bcbfbafb0783850', + '3daef80b45ef518d30c6db6db', + '56a187106e6e5fb88761024a5', + 'b5cd8b7a24054d7dc66e62f88', + '306eed0c4207d8db185f04afd', ] for output in outputs { assert rand.hex(25) == output @@ -277,16 +306,15 @@ fn test_rand_hex() { fn test_rand_ascii() { rand.seed([u32(0), 1]) outputs := [ - "2Z:&PeD'V;9=mn\$C>yKg'DIr%", - 'Ub7ix,}>I=QJki{%FHKv&K', - '1WStRylMO|p.R~qqRtr&AOEsd', - 'ykaG4#Z(2}s4', + r"l7'1Ute)i?4Efo$sX^sOk;s%m", + r"3}3s^l(PeNY>I8&'a>$)AW14*", + r'V.a^b>GN"\\9e-Vs"&.vS0"F_', + r"U-;S}OY+e>Ca>p'UD|7{}?6`x", + r'$/EN5*2w@/KdN~pU||c=*yn6|', + r'FsLkK{gFrPn)>EVW53uJLa<8?', + r'1#PB<"P}pLtY@F}^\TfNyCDB$', ] for output in outputs { assert rand.ascii(25) == output diff --git a/vlib/v/tests/bench/bench_rand_fill_buffer_from_set.v b/vlib/v/tests/bench/bench_rand_fill_buffer_from_set.v new file mode 100644 index 000000000..1c0a6191b --- /dev/null +++ b/vlib/v/tests/bench/bench_rand_fill_buffer_from_set.v @@ -0,0 +1,54 @@ +import os +import rand +import time +import rand.pcg32 + +const buf_len = os.getenv_opt('BUF_LEN') or { '100_000_000' }.int() +const nthreads = os.getenv_opt('VJOBS') or { '2' }.int() +const max_iterations = os.getenv_opt('MAX_ITERATIONS') or { '4' }.int() + +fn main() { + mut buf := []u8{len: buf_len} + mut arr := []thread{} + sw := time.new_stopwatch() + for i in 0 .. nthreads { + part_len := buf.len / nthreads + start := i * part_len + mut chunk := &WorkChunk{ + thread_id: i + // make the last thread fill the remaining characters too: + part: unsafe { buf[start..if i == nthreads - 1 { buf.len } else { start + part_len }] } + } + arr << spawn worker(mut chunk) + } + arr.wait() + elapsed := sw.elapsed().milliseconds() + mut histogram := []u64{len: 58} + for b in buf { + histogram[b]++ + } + println(' buf: ${buf#[..10]} ... ${buf#[-10..]}') + println(' histogram: ${histogram#[48..]}') + println('Total took ${elapsed:6}ms, VJOBS: ${nthreads:2}, MAX_ITERATIONS: ${max_iterations:5}, BUF_LEN: ${buf_len:6}') + println('') +} + +struct WorkChunk { + thread_id int +mut: + part []u8 +} + +const charset = '0123456789' + +fn worker(mut chunk WorkChunk) { + mut rng := rand.PRNG(pcg32.PCG32RNG{}) + sw := time.new_stopwatch() + for _ in 0 .. max_iterations { + rng.fill_buffer_from_set(charset, mut chunk.part) + } + elapsed_time_ns := sw.elapsed().nanoseconds() + fill_ms := u64(f64(elapsed_time_ns) / f64(max_iterations * 1_000_000)) + rand_character_ns := f64(elapsed_time_ns) / f64(max_iterations * chunk.part.len) + println(' thread ${chunk.thread_id:2}, took ${elapsed_time_ns / 1_000_000:6}ms, per fill: ${fill_ms:6}ms, per character: ${rand_character_ns:5.0}ns, part: ${chunk.part#[..3]}...${chunk.part#[-3..]}, part.len: ${chunk.part.len:6}') +} -- 2.39.5