Skip to content

Commit

Permalink
improve std bench and add significant-digits (#859)
Browse files Browse the repository at this point in the history
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.

<img width="747" alt="image"
src="https://github.com/nushell/nu_scripts/assets/4896754/ce98aebe-7c1b-4d8f-b2d0-3282d1ff3883">
<img width="747" alt="image"
src="https://github.com/nushell/nu_scripts/assets/4896754/508fef3e-ba70-40fd-8f9e-82b6ac608485">
<img width="747" alt="image"
src="https://github.com/nushell/nu_scripts/assets/4896754/af755f28-8506-4f4c-8bc2-91b35798855d">
  • Loading branch information
maxim-uvarov committed Jun 1, 2024
1 parent a921551 commit 5271d68
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 0 deletions.
103 changes: 103 additions & 0 deletions stdlib-candidate/std-rfc/bench.nu
Original file line number Diff line number Diff line change
@@ -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<duration>
# >
# ```
#
# > **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: "<mean> +/- <stddev>"
--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 {}
}
}
46 changes: 46 additions & 0 deletions stdlib-candidate/std-rfc/math/mod.nu
Original file line number Diff line number Diff line change
@@ -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}
}
}
2 changes: 2 additions & 0 deletions stdlib-candidate/std-rfc/mod.nu
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions stdlib-candidate/tests/bench.nu
Original file line number Diff line number Diff line change
@@ -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
}
21 changes: 21 additions & 0 deletions stdlib-candidate/tests/math.nu
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 2 additions & 0 deletions stdlib-candidate/tests/mod.nu
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 5271d68

Please sign in to comment.