v/vlib/v/tests/bench/math_big_gcd/bench_euclid.v

344 lines
7.8 KiB
V

module main
// Note: this benchmark is preferable to be compiled with: `v -prod -cg -gc boehm bench_euclid.v`
import math.big
import benchmark
import os
import v.tests.bench.math_big_gcd.prime {
DataI,
PrimeCfg,
PrimeSet,
}
interface TestDataI {
r big.Integer
aa big.Integer
bb big.Integer
}
type GCDSet = PrimeSet
type Clocks = map[string]benchmark.Benchmark
const (
empty_set = GCDSet{'1', '1', '1'}
with_dots = false
)
fn main() {
fp := os.join_path(@VROOT, prime.toml_path)
if !prime_file_exists(fp) {
panic('expected file |$fp| - not found.')
}
mut clocks := Clocks(map[string]benchmark.Benchmark{})
for algo in [
'euclid',
'gcd_binary',
//'u32binary',
//'u64binary'
] {
clocks[algo] = benchmark.new_benchmark()
}
// any test-prime-set needs to pass this predicate
// before used in the benchmark.
//
predicate_fn := fn (ps PrimeSet) bool {
cast_bi := bi_from_decimal_string
r := cast_bi(ps.r)
aa := r * cast_bi(ps.a)
bb := r * cast_bi(ps.b)
gcd := aa.gcd(bb)
return if [
gcd != big.one_int,
gcd == bb.gcd(aa),
gcd == r,
aa != bb,
(aa % gcd) == big.zero_int,
(bb % gcd) == big.zero_int,
].all(it == true)
{
true
} else {
false
}
}
cfgs := [
// root-prime a-prime b-prime
['s.3', 'xs.3', 's'],
['s.10', 's.10', 'm.all'],
['m.9', 's.10', 'xs'],
['l.40', 'xs.10', 's.5'],
['ml.20', 'm', 's.15'],
['xl', 's.10', 'l.15'],
['xxl', 'l.10', 'xl'],
['l.30', 'm.10', 'xxl'],
['xxl', 'xl', 'm.15'],
['mega', 'xl.6', 's'],
['xxl', 'xxxl.10', 'm.30'],
['mega', 'xxxl', 'mega'],
['giga.5', 'mega', 'giga'],
['crazy', 'mega', 'giga'],
['s', 'biggest', 'crazy'],
['biggest', 'crazy', 'giga'],
].map(PrimeCfg{it[0], it[1], it[2]})
println('\n$cfgs.len x Tests')
for i, prime_cfg in cfgs {
println('\n#-${i + 1}(Stack) "$prime_cfg"')
bench_euclid_vs_binary(prime_cfg, false, predicate_fn, mut clocks)
// just-to-be-sure, but makes no difference in this test.
// println('\n#-${i + 1}(Heap) "$prime_cfg"')
// bench_euclid_vs_binary(prime_cfg, true, predicate_fn, mut clocks)
}
println('')
for _, mut clock in clocks {
clock.stop()
}
println(clocks['euclid'].total_message('both algorithms '))
msg := [
'Seems to me as if euclid in big.Integer.gcd() performs better on ',
'very-small-integers up to 8-byte/u64. The tests #-1..5 show this.',
'The gcd_binary-algo seems to be perform better, the larger the numbers/buffers get.',
'On my machine, i see consistent gains between ~10-30-percent with :',
'\n',
'v -prod -cg -gc boehm bench_euclid.v',
'\n',
'This test covers multiplied primes up-to a length of 300-char-digits in ',
'a decimal-string. This equals (188-byte) == 47 x u32-values.',
'edit/change primes in $prime.toml_path',
'Improvements & critique are welcome : \n',
'https://lemire.me/blog/2013/12/26/fastest-way-to-compute-the-greatest-common-divisor/',
].join('\n')
println(msg)
}
fn run_benchmark(data []DataI, heap bool, mut clocks Clocks) bool {
mut testdata := []TestDataI{}
for elem in data {
// if elem is StackData || elem is HeapData{
// testdata << elem
// }
// TODO: this reads strange
if elem is StackData {
testdata << elem
}
if elem is HeapData {
testdata << elem
}
}
// some statistics
//
mut tmp := []int{cap: testdata.len * 3}
for set in testdata {
for prime in [set.r, set.aa, set.bb] {
bi, _ := prime.bytes()
tmp << bi_buffer_len(bi)
}
}
tmp.sort()
min_byte := tmp.first() * 4
max_byte := tmp.last() * 4
mut buffer_space := 0
for tmp.len != 0 {
buffer_space += tmp.pop()
}
// trying to balance prime-size and item-count
// minimum rounds is 100-times
//
mut rounds := 2000 / ((3 * buffer_space) / testdata.len)
rounds = if rounds < 100 { 100 } else { rounds }
ratio := (buffer_space * 4) / (testdata.len * 3)
msg := [
'avg-$ratio-byte/Prime, $min_byte-byte < Prime < $max_byte-byte \n',
'~${buffer_space * 4 / 1024}-Kb-',
if heap { 'Heap' } else { 'Stack' },
'-space for $testdata.len-items x ',
'$rounds-rounds',
].join('')
println(msg)
mut cycles := 0
for algo, mut clock in clocks {
cycles = 0
clock.step()
for cycles < rounds {
for set in testdata {
match algo {
'euclid' {
if set.r != set.aa.gcd(set.bb) {
eprintln('$algo failed ?')
clock.fail()
break
}
}
'gcd_binary' {
if set.r != set.aa.gcd_binary(set.bb) {
eprintln('$algo failed ?')
clock.fail()
break
}
}
else {
eprintln('unknown algo was "$algo"')
continue
}
}
} // eo-for over testdata
if with_dots {
print('.')
}
cycles += 1
} // eo-for cycles
if with_dots {
println('')
}
clock.ok()
clock.measure(algo)
} // eo-for-loop over algo, clock
return true
}
fn bench_euclid_vs_binary(test_config PrimeCfg, heap bool, predicate_fn fn (ps PrimeSet) bool, mut clocks Clocks) bool {
testprimes := prime.random_set(test_config) or { panic(err) }
// validate the test-data
//
gcd_primes := testprimes.map(prepare_and_test_gcd(it, predicate_fn)).filter(it != empty_set)
// just to make sure all generated primes are sane
//
assert gcd_primes.len == testprimes.len
// casting the decimal-strings into big.Integers
// here to avoid measuring string-parsing-cycles
// during later testing.
//
mut casted_sets := if heap { gcd_primes.map(unsafe {
DataI(&PrimeSet(&it)).cast<HeapData>()
}) } else { gcd_primes.map(unsafe {
DataI(&PrimeSet(&it)).cast<StackData>()
}) }
// ready use the primes in the benchmark
//
return run_benchmark(casted_sets, heap, mut clocks)
}
fn prepare_and_test_gcd(primeset PrimeSet, test fn (ps PrimeSet) bool) GCDSet {
if !primeset.predicate(test) {
eprintln('? Corrupt Testdata was ?')
dump(primeset)
return empty_set // {'1', '1', '1'}
}
cast_bi := bi_from_decimal_string
r := cast_bi(primeset.r)
aa := cast_bi(primeset.a) * r
bb := cast_bi(primeset.b) * r
gcd := aa.gcd(bb)
return GCDSet{'$gcd', '$aa', '$bb'}
}
fn prime_file_exists(path string) bool {
return os.is_readable(path)
}
// bi_from_decimal_string converts a string-of-decimals into
// a math.big.Integer using the big.Integers 'from_radix-fn'
//
pub fn bi_from_decimal_string(s string) big.Integer {
return big.integer_from_radix(s, u32(10)) or {
msg := [
'Cannot convert prime from decimal-string.',
'prime was : "$s"\n',
].join('\n')
panic(msg)
}
}
// need the bi.digits.len - during test only - to calculate
// the size of big.Integers-buffer
//
fn bi_buffer_len(input []u8) int {
if input.len == 0 {
return 0
}
// pad input
mut padded_input := []u8{len: ((input.len + 3) & ~0x3) - input.len, cap: (input.len + 3) & ~0x3, init: 0x0}
padded_input << input
mut digits := []u32{len: padded_input.len / 4}
// combine every 4 bytes into a u32 and insert into n.digits
for i := 0; i < padded_input.len; i += 4 {
x3 := u32(padded_input[i])
x2 := u32(padded_input[i + 1])
x1 := u32(padded_input[i + 2])
x0 := u32(padded_input[i + 3])
val := (x3 << 24) | (x2 << 16) | (x1 << 8) | x0
digits[(padded_input.len - i) / 4 - 1] = val
}
return digits.len
}
[heap]
pub struct HeapData {
pub mut:
r big.Integer
aa big.Integer
bb big.Integer
}
pub fn (hd HeapData) to_primeset() PrimeSet {
return PrimeSet{
r: '$hd.r'
a: '$hd.aa'
b: '$hd.bb'
}
}
pub fn (hd HeapData) from_primeset(p PrimeSet) DataI {
return DataI(HeapData{
r: bi_from_decimal_string(p.r)
aa: bi_from_decimal_string(p.a)
bb: bi_from_decimal_string(p.b)
})
}
pub struct StackData {
pub mut:
r big.Integer
aa big.Integer
bb big.Integer
}
pub fn (sd StackData) to_primeset() PrimeSet {
return PrimeSet{
r: '$sd.r'
a: '$sd.aa'
b: '$sd.bb'
}
}
pub fn (sd StackData) from_primeset(p PrimeSet) DataI {
return DataI(StackData{
r: bi_from_decimal_string(p.r)
aa: bi_from_decimal_string(p.a)
bb: bi_from_decimal_string(p.b)
})
}