From 5271d68b7a6770eeade257b298089d14c68ab62b Mon Sep 17 00:00:00 2001 From: Maxim Uvarov Date: Sat, 1 Jun 2024 17:54:45 +0800 Subject: [PATCH] improve `std bench` and add `significant-digits` (#859) Hi! I propose several changes to `std bench`: - Remove insignificant precision by default (can be reverted with `--sign-digits=0`). - Remove the 'times' field by default (can be returned with `--list-timings`). - Add an option to set time units (inactive by default). For removing insignificant precision, I needed a new command `significant-digits` which is also included in this PR. This command has tests. image image image --- stdlib-candidate/std-rfc/bench.nu | 103 +++++++++++++++++++++++++++ stdlib-candidate/std-rfc/math/mod.nu | 46 ++++++++++++ stdlib-candidate/std-rfc/mod.nu | 2 + stdlib-candidate/tests/bench.nu | 22 ++++++ stdlib-candidate/tests/math.nu | 21 ++++++ stdlib-candidate/tests/mod.nu | 2 + 6 files changed, 196 insertions(+) create mode 100644 stdlib-candidate/std-rfc/bench.nu create mode 100644 stdlib-candidate/std-rfc/math/mod.nu create mode 100644 stdlib-candidate/tests/bench.nu create mode 100644 stdlib-candidate/tests/math.nu diff --git a/stdlib-candidate/std-rfc/bench.nu b/stdlib-candidate/std-rfc/bench.nu new file mode 100644 index 0000000..3c822ef --- /dev/null +++ b/stdlib-candidate/std-rfc/bench.nu @@ -0,0 +1,103 @@ +# A proposal for improving the original `std bench` command by @amtoine +# https://github.com/nushell/nushell/blob/31f3d2f6642b585f0d88192724723bf0ce330ecf/crates/nu-std/std/mod.nu#L134 + +use ./math + +# convert an integer amount of nanoseconds to a real duration +def "from ns" [ + --units: string # units to convert duration to (min, sec, ms, µs, ns) + --sign-digits: int # a number of first non-zero digits to keep (default 4; set 0 to disable rounding) +] { + if $sign_digits == 0 {} else { + math significant-digits $sign_digits + } + | $"($in)ns" + | into duration + | if $units != null { + format duration $units + } else {} +} + +# run a piece of `nushell` code multiple times and measure the time of execution. +# +# this command returns a benchmark report of the following form: +# ``` +# record< +# mean: duration +# min: duration +# max: duration +# std: duration +# times: list +# > +# ``` +# +# > **Note** +# > `std bench --pretty` will return a `string`. +# > the `--units` option will convert all durations to strings +# +# # Examples +# measure the performance of simple addition +# > bench {1 + 2} +# ╭──────┬───────────╮ +# │ mean │ 716ns │ +# │ min │ 583ns │ +# │ max │ 4µs 541ns │ +# │ std │ 549ns │ +# ╰──────┴───────────╯ +# +# get a pretty benchmark report +# > std bench {1 + 2} --pretty +# 922ns +/- 2µs 40ns +# +# format results as `ms` +# > bench {sleep 0.1sec; 1 + 2} --units ms --rounds 5 +# ╭──────┬───────────╮ +# │ mean │ 104.90 ms │ +# │ min │ 103.60 ms │ +# │ max │ 105.90 ms │ +# │ std │ 0.75 ms │ +# ╰──────┴───────────╯ +# +# measure the performance of simple addition with 1ms delay and output each timing +# > bench {sleep 1ms; 1 + 2} --rounds 2 --list-timings | table -e +# ╭───────┬─────────────────────╮ +# │ mean │ 1ms 272µs │ +# │ min │ 1ms 259µs │ +# │ max │ 1ms 285µs │ +# │ std │ 13µs 370ns │ +# │ │ ╭─────────────────╮ │ +# │ times │ │ 1ms 285µs 791ns │ │ +# │ │ │ 1ms 259µs 42ns │ │ +# │ │ ╰─────────────────╯ │ +# ╰───────┴─────────────────────╯ +export def main [ + code: closure # the piece of `nushell` code to measure the performance of + --rounds (-n): int = 50 # the number of benchmark rounds (hopefully the more rounds the less variance) + --verbose (-v) # be more verbose (namely prints the progress) + --pretty # shows the results in human-readable format: " +/- " + --units: string # units to convert duration to (min, sec, ms, µs, ns) + --list-timings # list all rounds' timings in a `times` field + --sign-digits: int = 4 # a number of first non-zero digits to keep (default 4; set 0 to disable rounding) +] { + let times = seq 1 $rounds + | each {|i| + if $verbose { print -n $"($i) / ($rounds)\r" } + timeit { do $code } | into int | into float + } + + if $verbose { print $"($rounds) / ($rounds)" } + + { + mean: ($times | math avg | from ns --units $units --sign-digits=$sign_digits) + min: ($times | math min | from ns --units $units --sign-digits=$sign_digits) + max: ($times | math max | from ns --units $units --sign-digits=$sign_digits) + std: ($times | math stddev | from ns --units $units --sign-digits=$sign_digits) + } + | if $pretty { + $"($in.mean) +/- ($in.std)" + } else { + if $list_timings { + merge { times: ($times | each { from ns --units $units --sign-digits 0 }) } + } else {} + } +} diff --git a/stdlib-candidate/std-rfc/math/mod.nu b/stdlib-candidate/std-rfc/math/mod.nu new file mode 100644 index 0000000..68ad453 --- /dev/null +++ b/stdlib-candidate/std-rfc/math/mod.nu @@ -0,0 +1,46 @@ + +use std iter scan + +# replace all insignificant digits with 0 +# +# | Significant Digits | Maximum Relative Error | +# |--------------------|------------------------| +# | 1 | 50% | +# | 2 | 5% | +# | 3 | 0.5% | +# | 4 | 0.05% | +# | 5 | 0.005% | +# | 6 | 0.0005% | +# +# > 0.0000012346789 | significant-digits 2 +# 0.0000012 +# +# > 1.2346789 | significant-digits 3 +# 1.23 +# +# > 1sec / 3 | math significant-digits +# 333ms +export def 'significant-digits' [ + n: int = 4 # a number of first non-zero digits to keep +]: [int -> int, float -> float, duration -> duration] { + let $input = $in + let $chars = $input | into string | split chars + + let $rounded_str = $chars + | scan --noinit 0 {|ind i| + if $i =~ '\d' { + if $ind == 0 and $i == '0' { + $ind + } else { $ind + 1 } + } else {$ind} + } + | zip $chars + | each {|i| if $i.1 =~ '\d' and $i.0 > $n {'0'} else {$i.1}} + | str join + + match ($input | describe) { + 'duration' => {$rounded_str | into duration} + 'int' => {$rounded_str | into int} + 'float' => {$rounded_str | into float} + } +} diff --git a/stdlib-candidate/std-rfc/mod.nu b/stdlib-candidate/std-rfc/mod.nu index 58b7709..278a21e 100644 --- a/stdlib-candidate/std-rfc/mod.nu +++ b/stdlib-candidate/std-rfc/mod.nu @@ -1,6 +1,8 @@ # modules export module record/ export module str/ +export module math/ # commands export use bulk-rename.nu * export use set-env.nu * +export use bench.nu diff --git a/stdlib-candidate/tests/bench.nu b/stdlib-candidate/tests/bench.nu new file mode 100644 index 0000000..0e2c41f --- /dev/null +++ b/stdlib-candidate/tests/bench.nu @@ -0,0 +1,22 @@ +use std assert +use ../std-rfc bench + +export def "test bench-units" [] { + let $test = bench {sleep 0.001sec; 1 + 2} --units ns --rounds 2 | get mean + assert str contains $test " ns" +} + +export def "test bench-timings" [] { + let $test = bench {1 + 2} --rounds 3 --list-timings | get times | length + assert equal $test 3 +} + +export def "test bench-pretty" [] { + let $test = (bench {1 + 2} --rounds 3 --pretty) =~ '\d.* \+/- \d' + assert equal $test true +} + +export def "test bench-sign-digits" [] { + let $test = bench {sleep 1ms} --sign-digits 1 --rounds 5 | get min + assert equal $test 1ms +} diff --git a/stdlib-candidate/tests/math.nu b/stdlib-candidate/tests/math.nu new file mode 100644 index 0000000..c719adc --- /dev/null +++ b/stdlib-candidate/tests/math.nu @@ -0,0 +1,21 @@ +use std assert +use ../std-rfc/math + +#[test] +export def "test significant-digits-decimals" [] { + assert equal (0.0000012346789 | math significant-digits 2) 0.0000012 + assert equal (11 / 3 | math significant-digits 6) 3.66666 + assert not equal (0.0000012346789 | math significant-digits 2) 0.00000123 + assert equal (1.999999 | math significant-digits 1) 1.0 +} + +#[test] +export def "test significant-digits-duration" [] { + assert equal (2min / 7 | math significant-digits 3) 17100000000ns + assert equal (1sec | math significant-digits 3) 1000000000ns +} + +#[test] +export def "test significant-digits-ints" [] { + assert equal (123456 | math significant-digits 2) 120000 +} diff --git a/stdlib-candidate/tests/mod.nu b/stdlib-candidate/tests/mod.nu index 350dbbb..34dc62f 100644 --- a/stdlib-candidate/tests/mod.nu +++ b/stdlib-candidate/tests/mod.nu @@ -1,3 +1,5 @@ export module bulk-rename.nu export module record.nu export module str_xpend.nu +export module math.nu +export module bench.nu