Skip to content

Commit

Permalink
rand: add PRNG.fill_buffer_from_set/2 (#21037)
Browse files Browse the repository at this point in the history
  • Loading branch information
spytheman committed Mar 16, 2024
1 parent 4221522 commit a1c6377
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 31 deletions.
16 changes: 15 additions & 1 deletion vlib/rand/rand.c.v
Expand Up @@ -93,14 +93,15 @@ 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 ''
}
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 {
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions vlib/rand/rand.js.v
Expand Up @@ -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 := ''
#
Expand Down
12 changes: 12 additions & 0 deletions vlib/rand/rand.v
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
88 changes: 58 additions & 30 deletions vlib/rand/random_numbers_test.v
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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=&#2QJki{%FHKv&K',
'1WStRylMO|p.R~qqRtr&AOEsd',
'yka<GPZ&m+r0^Zi!ShB*1dU~W',
'uDA?.zU2X,<DkKT#_-halW\\ki',
'fsx!@uRc?re/fSPXj`Y&\\BU}p',
'fI_qM"):2;CUno!<dX:Yv*FX$',
'FnA(Fr|D`WZVWEzp<k)O;auub',
"QRkxH!kjXh&/j{)uSe&{D'v?|",
"_CyaU\$z':#}At*v2|xDu6w=;1",
r"KqdNI|*bDh42kn'z-}}nhmKd~",
r'IZ4wVRC-Q3@TviD>G4#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
Expand Down
54 changes: 54 additions & 0 deletions 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}')
}

0 comments on commit a1c6377

Please sign in to comment.