DavisVaughan / bench

High Precision Timing of R Expressions

Home Page:http://bench.r-lib.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

bench

CRAN status R-CMD-check Codecov test coverage

The goal of bench is to benchmark code, tracking execution time, memory allocations and garbage collections.

Installation

You can install the release version from CRAN with:

install.packages("bench")

Or you can install the development version from GitHub with:

# install.packages("remotes")
remotes::install_github("r-lib/bench")

Features

bench::mark() is used to benchmark one or a series of expressions, we feel it has a number of advantages over alternatives.

  • Always uses the highest precision APIs available for each operating system (often nanoseconds).
  • Tracks memory allocations for each expression.
  • Tracks the number and type of R garbage collections per expression iteration.
  • Verifies equality of expression results by default, to avoid accidentally benchmarking inequivalent code.
  • Has bench::press(), which allows you to easily perform and combine benchmarks across a large grid of values.
  • Uses adaptive stopping by default, running each expression for a set amount of time rather than for a specific number of iterations.
  • Expressions are run in batches and summary statistics are calculated after filtering out iterations with garbage collections. This allows you to isolate the performance and effects of garbage collection on running time (for more details see Neal 2014).

The times and memory usage are returned as custom objects which have human readable formatting for display (e.g. 104ns) and comparisons (e.g. x$mem_alloc > "10MB").

There is also full support for plotting with ggplot2 including custom scales and formatting.

Continuous benchmarking

This feature is still in early and active development, but the brave can test it out.

You can setup continuous benchmarking for an R package by adding .R scripts containing one or more calls to bench::mark() in the bench/ directory of an R package. Then from any CI service you can then fetch previous results, run the benchmarks and push the results back to the repository with the following.

bench::cb_fetch()
bench::cb_run()
bench::cb_push()

To retrieve the full dataset of benchmark results locally use the following.

bench::cb_fetch()
results <- bench::cb_read()

And to plot the benchmark times per commit

bench::cb_plot_time(results)

Usage

bench::mark()

Benchmarks can be run with bench::mark(), which takes one or more expressions to benchmark against each other.

library(bench)
set.seed(42)
dat <- data.frame(x = runif(10000, 1, 1000), y=runif(10000, 1, 1000))

bench::mark() will throw an error if the results are not equivalent, so you don’t accidentally benchmark inequivalent code.

bench::mark(
  dat[dat$x > 500, ],
  dat[which(dat$x > 499), ],
  subset(dat, x > 500))
#> Error: Each result must equal the first result:
#> `dat[dat$x > 500, ]` does not equal `dat[which(dat$x > 499), ]`

Results are easy to interpret, with human readable units.

bnch <- bench::mark(
  dat[dat$x > 500, ],
  dat[which(dat$x > 500), ],
  subset(dat, x > 500))
bnch
#> # A tibble: 3 × 6
#>   expression                     min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>                <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 dat[dat$x > 500, ]           278µs    392µs     2481.     377KB     16.6
#> 2 dat[which(dat$x > 500), ]    205µs    266µs     3756.     260KB     17.7
#> 3 subset(dat, x > 500)         355µs    462µs     2162.     510KB     17.3

By default the summary uses absolute measures, however relative results can be obtained by using relative = TRUE in your call to bench::mark() or calling summary(relative = TRUE) on the results.

summary(bnch, relative = TRUE)
#> # A tibble: 3 × 6
#>   expression                  min median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>                <dbl>  <dbl>     <dbl>     <dbl>    <dbl>
#> 1 dat[dat$x > 500, ]         1.36   1.48      1.15      1.45     1   
#> 2 dat[which(dat$x > 500), ]  1      1         1.74      1        1.06
#> 3 subset(dat, x > 500)       1.73   1.74      1         1.96     1.04

bench::press()

bench::press() is used to run benchmarks against a grid of parameters. Provide setup and benchmarking code as a single unnamed argument then define sets of values as named arguments. The full combination of values will be expanded and the benchmarks are then pressed together in the result. This allows you to benchmark a set of expressions across a wide variety of input sizes, perform replications and other useful tasks.

set.seed(42)

create_df <- function(rows, cols) {
  as.data.frame(setNames(
    replicate(cols, runif(rows, 1, 100), simplify = FALSE),
    rep_len(c("x", letters), cols)))
}

results <- bench::press(
  rows = c(1000, 10000),
  cols = c(2, 10),
  {
    dat <- create_df(rows, cols)
    bench::mark(
      min_iterations = 100,
      bracket = dat[dat$x > 500, ],
      which = dat[which(dat$x > 500), ],
      subset = subset(dat, x > 500)
    )
  }
)
#> Running with:
#>    rows  cols
#> 1  1000     2
#> 2 10000     2
#> 3  1000    10
#> 4 10000    10
results
#> # A tibble: 12 × 8
#>    expression  rows  cols      min   median `itr/sec` mem_alloc `gc/sec`
#>    <bch:expr> <dbl> <dbl> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#>  1 bracket     1000     2     30µs   35.8µs    25614.   15.84KB     12.8
#>  2 which       1000     2   26.3µs   34.6µs    25452.    7.91KB     12.7
#>  3 subset      1000     2   52.6µs   62.1µs    13154.    27.7KB     10.4
#>  4 bracket    10000     2   62.1µs   69.3µs    13811.  156.46KB     41.1
#>  5 which      10000     2   47.9µs   53.8µs    17927.   78.23KB     24.9
#>  6 subset     10000     2  111.7µs  131.3µs     7309.  273.79KB     39.4
#>  7 bracket     1000    10   65.3µs   82.6µs    11875.   47.52KB     15.4
#>  8 which       1000    10   60.1µs   73.5µs    13446.    7.91KB     18.8
#>  9 subset      1000    10   90.5µs  110.4µs     8852.   59.38KB     15.1
#> 10 bracket    10000    10    148µs    168µs     5733.   469.4KB     49.2
#> 11 which      10000    10   69.9µs   88.7µs    11382.   78.23KB     18.7
#> 12 subset     10000    10  207.6µs  236.8µs     4088.  586.73KB     47.4

Plotting

ggplot2::autoplot() can be used to generate an informative default plot. This plot is colored by gc level (0, 1, or 2) and faceted by parameters (if any). By default it generates a beeswarm plot, however you can also specify other plot types (jitter, ridge, boxplot, violin). See ?autoplot.bench_mark for full details.

ggplot2::autoplot(results)

You can also produce fully custom plots by un-nesting the results and working with the data directly.

system_time()

bench also includes system_time(), a higher precision alternative to system.time().

bench::system_time({ i <- 1; while(i < 1e7) i <- i + 1 })
#> process    real 
#>   218ms   219ms
bench::system_time(Sys.sleep(.5))
#> process    real 
#>   105µs   504ms

Alternatives

About

High Precision Timing of R Expressions

http://bench.r-lib.org/

License:Other


Languages

Language:R 86.7%Language:C 13.3%