Skip to content

Introduction

We make sure that the timer is not TRUE, by setting it to ten hours.

library(cli)
# 10 hours
cli:::cli_tick_set(10 * 60 * 60 * 1000)
cli_tick_reset()
#> NULL
`__cli_update_due`
#> [1] FALSE

R benchmarks

The timer

fun <- function() NULL
ben_st <- bench::mark(
  `__cli_update_due`,
  fun(),
  .Call(ccli_tick_reset),
  interactive(),
  check = FALSE
)
ben_st
#> # A tibble: 4 × 6
#>   expression                  min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>             <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 __cli_update_due        10.13ns     20ns 47720851.        0B        0
#> 2 fun()                  140.05ns    171ns  4409462.        0B        0
#> 3 .Call(ccli_tick_reset) 110.01ns    131ns  7204075.        0B        0
#> 4 interactive()            8.96ns     20ns 61259273.        0B        0
ben_st2 <- bench::mark(
  if (`__cli_update_due`) foobar()
)
ben_st2
#> # A tibble: 1 × 6
#>   expression                       min median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>                     <bch> <bch:>     <dbl> <bch:byt>    <dbl>
#> 1 if (`__cli_update_due`) fooba…  40ns 60.1ns 17324015.        0B        0

cli_progress_along()

seq <- 1:100000
ta <- cli_progress_along(seq)
bench::mark(seq[[1]], ta[[1]])
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 seq[[1]]      130ns    150ns  6401721.        0B        0
#> 2 ta[[1]]       150ns    170ns  5585169.        0B        0

for loop

This is the baseline:

f0 <- function(n = 1e5) {
  x <- 0
  seq <- 1:n
  for (i in seq) {
    x <- x + i %% 2
  }
  x
}

With progress bars:

fp <- function(n = 1e5) {
  x <- 0
  seq <- 1:n
  for (i in cli_progress_along(seq)) {
    x <- x + seq[[i]] %% 2
  }
  x
}

Overhead per iteration:

ben_taf <- bench::mark(f0(), fp())
ben_taf
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 f0()         11.9ms     12ms      83.3    25.1KB    1374.
#> 2 fp()         13.6ms   13.6ms      73.2    83.1KB     707.
(ben_taf$median[2] - ben_taf$median[1]) / 1e5
#> [1] 16.3ns
ben_taf2 <- bench::mark(f0(1e6), fp(1e6))
#> Warning: Some expressions had a GC in every iteration; so filtering is
#> disabled.
ben_taf2
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 f0(1e+06)     133ms    138ms      6.68        0B     60.1
#> 2 fp(1e+06)     145ms    146ms      6.85    1.84KB     61.6
(ben_taf2$median[2] - ben_taf2$median[1]) / 1e6
#> [1] 8.09ns
ben_taf3 <- bench::mark(f0(1e7), fp(1e7))
#> Warning: Some expressions had a GC in every iteration; so filtering is
#> disabled.
ben_taf3
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 f0(1e+07)     1.38s    1.38s     0.725        0B     67.4
#> 2 fp(1e+07)     1.51s    1.51s     0.660    1.84KB     60.7
(ben_taf3$median[2] - ben_taf3$median[1]) / 1e7
#> [1] 13.5ns
ben_taf4 <- bench::mark(f0(1e8), fp(1e8))
#> Warning: Some expressions had a GC in every iteration; so filtering is
#> disabled.
ben_taf4
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 f0(1e+08)     13.5s    13.5s    0.0742        0B     31.1
#> 2 fp(1e+08)     14.4s    14.4s    0.0696    1.84KB     29.2
(ben_taf4$median[2] - ben_taf4$median[1]) / 1e8
#> [1] 8.81ns

Mapping with lapply()

This is the baseline:

f0 <- function(n = 1e5) {
  seq <- 1:n
  ret <- lapply(seq, function(x) {
    x %% 2
  })
  invisible(ret)
}

With an index vector:

f01 <- function(n = 1e5) {
  seq <- 1:n
  ret <- lapply(seq_along(seq), function(i) {
    seq[[i]] %% 2
  })
  invisible(ret)
}

With progress bars:

fp <- function(n = 1e5) {
  seq <- 1:n
  ret <- lapply(cli_progress_along(seq), function(i) {
    seq[[i]] %% 2
  })
  invisible(ret)
}

Overhead per iteration:

ben_tam <- bench::mark(f0(), f01(), fp())
#> Warning: Some expressions had a GC in every iteration; so filtering is
#> disabled.
ben_tam
#> # A tibble: 3 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 f0()         72.7ms   82.1ms      9.19     781KB     18.4
#> 2 f01()        86.9ms  101.9ms      9.88     781KB     19.8
#> 3 fp()        119.1ms  127.7ms      7.52     783KB     16.9
(ben_tam$median[3] - ben_tam$median[1]) / 1e5
#> [1] 457ns
ben_tam2 <- bench::mark(f0(1e6), f01(1e6), fp(1e6))
#> Warning: Some expressions had a GC in every iteration; so filtering is
#> disabled.
ben_tam2
#> # A tibble: 3 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 f0(1e+06)  732.42ms 732.42ms     1.37     7.63MB     4.10
#> 2 f01(1e+06) 984.29ms 984.29ms     1.02     7.63MB     5.08
#> 3 fp(1e+06)     1.29s    1.29s     0.774    7.63MB     3.87
(ben_tam2$median[3] - ben_tam2$median[1]) / 1e6
#> [1] 559ns
(ben_tam2$median[3] - ben_tam2$median[2]) / 1e6
#> [1] 308ns

Mapping with purrr

This is the baseline:

f0 <- function(n = 1e5) {
  seq <- 1:n
  ret <- purrr::map(seq, function(x) {
    x %% 2
  })
  invisible(ret)
}

With index vector:

f01 <- function(n = 1e5) {
  seq <- 1:n
  ret <- purrr::map(seq_along(seq), function(i) {
    seq[[i]] %% 2
  })
  invisible(ret)
}

With progress bars:

fp <- function(n = 1e5) {
  seq <- 1:n
  ret <- purrr::map(cli_progress_along(seq), function(i) {
    seq[[i]] %% 2
  })
  invisible(ret)
}

Overhead per iteration:

ben_pur <- bench::mark(f0(), f01(), fp())
ben_pur
#> # A tibble: 3 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 f0()         65.2ms   65.3ms      15.2     884KB     9.13
#> 2 f01()        81.2ms   81.4ms      12.2     781KB     6.09
#> 3 fp()         83.4ms   89.5ms      11.1     783KB     7.39
(ben_pur$median[3] - ben_pur$median[1]) / 1e5
#> [1] 242ns
(ben_pur$median[3] - ben_pur$median[2]) / 1e5
#> [1] 81.4ns
ben_pur2 <- bench::mark(f0(1e6), f01(1e6), fp(1e6))
#> Warning: Some expressions had a GC in every iteration; so filtering is
#> disabled.
ben_pur2
#> # A tibble: 3 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 f0(1e+06)  742.47ms 742.47ms     1.35     7.63MB     2.69
#> 2 f01(1e+06)    1.22s    1.22s     0.822    7.63MB     2.47
#> 3 fp(1e+06)     1.11s    1.11s     0.905    7.63MB     2.71
(ben_pur2$median[3] - ben_pur2$median[1]) / 1e6
#> [1] 363ns
(ben_pur2$median[3] - ben_pur2$median[2]) / 1e6
#> [1] 1ns

ticking()

f0 <- function(n = 1e5) {
  i <- 0
  x <- 0 
  while (i < n) {
    x <- x + i %% 2
    i <- i + 1
  }
  x
}
fp <- function(n = 1e5) {
  i <- 0
  x <- 0 
  while (ticking(i < n)) {
    x <- x + i %% 2
    i <- i + 1
  }
  x
}
ben_tk <- bench::mark(f0(), fp())
#> Warning: Some expressions had a GC in every iteration; so filtering is
#> disabled.
ben_tk
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 f0()        12.85ms     14ms     5.00     40.2KB    0.714
#> 2 fp()          3.77s    3.77s     0.265   100.7KB    2.38
(ben_tk$median[2] - ben_tk$median[1]) / 1e5
#> [1] 37.6µs

Traditional API

f0 <- function(n = 1e5) {
  x <- 0
  for (i in 1:n) {
    x <- x + i %% 2
  }
  x
}
fp <- function(n = 1e5) {
  cli_progress_bar(total = n)
  x <- 0
  for (i in 1:n) {
    x <- x + i %% 2
    cli_progress_update()
  }
  x
}
ff <- function(n = 1e5) {
  cli_progress_bar(total = n)
  x <- 0
  for (i in 1:n) {
    x <- x + i %% 2
    if (`__cli_update_due`) cli_progress_update()
  }
  x
}
ben_api <- bench::mark(f0(), ff(), fp())
#> Warning: Some expressions had a GC in every iteration; so filtering is
#> disabled.
ben_api
#> # A tibble: 3 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 f0()        11.64ms  11.93ms    72.6      19.9KB     5.89
#> 2 ff()        21.05ms  21.33ms    43.6      28.5KB     3.96
#> 3 fp()          2.12s    2.12s     0.471    25.9KB     2.36
(ben_api$median[3] - ben_api$median[1]) / 1e5
#> [1] 21.1µs
(ben_api$median[2] - ben_api$median[1]) / 1e5
#> [1] 94ns
ben_api2 <- bench::mark(f0(1e6), ff(1e6), fp(1e6))
#> Warning: Some expressions had a GC in every iteration; so filtering is
#> disabled.
ben_api2
#> # A tibble: 3 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 f0(1e+06)   126.1ms  137.5ms    7.31          0B     7.31
#> 2 ff(1e+06)     237ms  237.6ms    4.07      1.84KB     4.07
#> 3 fp(1e+06)     22.3s    22.3s    0.0449    1.84KB     2.24
(ben_api2$median[3] - ben_api2$median[1]) / 1e6
#> [1] 22.2µs
(ben_api2$median[2] - ben_api2$median[1]) / 1e6
#> [1] 100ns

C benchmarks

Baseline function:

SEXP test_baseline() {
  int i;
  int res = 0;
  for (i = 0; i < 2000000000; i++) {
    res += i % 2;
  }
  return ScalarInteger(res);
}

Switch + modulo check:

SEXP test_modulo(SEXP progress) {
  int i;
  int res = 0;
  int progress_ = LOGICAL(progress)[0];
  for (i = 0; i < 2000000000; i++) {
    if (i % 10000 == 0 && progress_) cli_progress_set(R_NilValue, i);
    res += i % 2;
  }
  return ScalarInteger(res);
}

cli progress bar API:

SEXP test_cli() {
  int i;
  int res = 0;
  SEXP bar = PROTECT(cli_progress_bar(2000000000, NULL));
  for (i = 0; i < 2000000000; i++) {
    if (CLI_SHOULD_TICK) cli_progress_set(bar, i);
    res += i % 2;
  }
  cli_progress_done(bar);
  UNPROTECT(1);
  return ScalarInteger(res);
}
SEXP test_cli_unroll() {
  int i = 0;
  int res = 0;
  SEXP bar = PROTECT(cli_progress_bar(2000000000, NULL));
  int s, final, step = 2000000000 / 100000;
  for (s = 0; s < 100000; s++) {
    if (CLI_SHOULD_TICK) cli_progress_set(bar, i);
    final = (s + 1) * step;
    for (i = s * step; i < final; i++) {
      res += i % 2;
    }
  }
  cli_progress_done(bar);
  UNPROTECT(1);
  return ScalarInteger(res);
}
library(progresstest)
ben_c <- bench::mark(
  test_baseline(),
  test_modulo(),
  test_cli(),
  test_cli_unroll()
)
ben_c
#> # A tibble: 4 × 6
#>   expression             min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>        <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 test_baseline()   631.45ms 631.45ms     1.58     2.08KB        0
#> 2 test_modulo()        1.24s    1.24s     0.807    2.23KB        0
#> 3 test_cli()           1.24s    1.24s     0.808   23.93KB        0
#> 4 test_cli_unroll() 620.16ms 620.16ms     1.61     3.59KB        0
(ben_c$median[3] - ben_c$median[1]) / 2000000000
#> [1] 1ns

Display update

We only update the display a fixed number of times per second. (Currently maximum five times per second.)

Let’s measure how long a single update takes.

Iterator with a bar

cli_progress_bar(total = 100000)
bench::mark(cli_progress_update(force = TRUE), max_iterations = 10000)
#>    0% | ETA:  4m
#>    0% | ETA:  2h
#>    0% | ETA:  1h
#>    0% | ETA:  1h
#>    0% | ETA:  1h
#>    0% | ETA: 44m
#>    0% | ETA: 39m
#>    0% | ETA: 35m
#>    0% | ETA: 33m
#>    0% | ETA: 30m
#>    0% | ETA: 28m
#>    0% | ETA: 27m
#>    0% | ETA: 25m
#>    0% | ETA: 24m
#>    0% | ETA: 23m
#>    0% | ETA: 22m
#>    0% | ETA: 22m
#>    0% | ETA: 21m
#>    0% | ETA: 20m
#>    0% | ETA: 20m
#>    0% | ETA: 19m
#>    0% | ETA: 19m
#>    0% | ETA: 18m
#>    0% | ETA: 18m
#>    0% | ETA: 18m
#>    0% | ETA: 17m
#>    0% | ETA: 17m
#>    0% | ETA: 17m
#>    0% | ETA: 16m
#>    0% | ETA: 16m
#>    0% | ETA: 16m
#>    0% | ETA: 16m
#>    0% | ETA: 16m
#>    0% | ETA: 15m
#>    0% | ETA: 15m
#>    0% | ETA: 15m
#>    0% | ETA: 15m
#>    0% | ETA: 15m
#>    0% | ETA: 15m
#>    0% | ETA: 14m
#>    0% | ETA: 14m
#>    0% | ETA: 14m
#>    0% | ETA: 14m
#>    0% | ETA: 14m
#>    0% | ETA: 14m
#>    0% | ETA: 14m
#>    0% | ETA: 14m
#>    0% | ETA: 14m
#>    0% | ETA: 14m
#>    0% | ETA: 13m
#>    0% | ETA: 13m
#>    0% | ETA: 13m
#>    0% | ETA: 13m
#>    0% | ETA: 13m
#>    0% | ETA: 13m
#>    0% | ETA: 13m
#>    0% | ETA: 13m
#>    0% | ETA: 13m
#>    0% | ETA: 13m
#>    0% | ETA: 13m
#>    0% | ETA: 13m
#>    0% | ETA: 13m
#>    0% | ETA: 13m
#>    0% | ETA: 13m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#>    0% | ETA: 12m
#> # A tibble: 1 × 6
#>   expression                       min median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>                    <bch:> <bch:>     <dbl> <bch:byt>    <dbl>
#> 1 cli_progress_update(force = … 5.45ms 5.55ms      178.    1.34MB     2.02
cli_progress_done()

Iterator without a bar

cli_progress_bar(total = NA)
bench::mark(cli_progress_update(force = TRUE), max_iterations = 10000)
#> ⠙ 1 done (431/s) | 3ms
#> ⠹ 2 done (75/s) | 27ms
#> ⠸ 3 done (90/s) | 34ms
#> ⠼ 4 done (101/s) | 40ms
#> ⠴ 5 done (109/s) | 47ms
#> ⠦ 6 done (114/s) | 53ms
#> ⠧ 7 done (119/s) | 60ms
#> ⠇ 8 done (122/s) | 66ms
#> ⠏ 9 done (125/s) | 72ms
#> ⠋ 10 done (128/s) | 79ms
#> ⠙ 11 done (130/s) | 85ms
#> ⠹ 12 done (132/s) | 92ms
#> ⠸ 13 done (133/s) | 98ms
#> ⠼ 14 done (135/s) | 105ms
#> ⠴ 15 done (136/s) | 111ms
#> ⠦ 16 done (137/s) | 118ms
#> ⠧ 17 done (137/s) | 125ms
#> ⠇ 18 done (138/s) | 131ms
#> ⠏ 19 done (138/s) | 138ms
#> ⠋ 20 done (139/s) | 144ms
#> ⠙ 21 done (140/s) | 151ms
#> ⠹ 22 done (140/s) | 157ms
#> ⠸ 23 done (141/s) | 164ms
#> ⠼ 24 done (141/s) | 170ms
#> ⠴ 25 done (142/s) | 177ms
#> ⠦ 26 done (142/s) | 183ms
#> ⠧ 27 done (143/s) | 190ms
#> ⠇ 28 done (143/s) | 196ms
#> ⠏ 29 done (144/s) | 203ms
#> ⠋ 30 done (144/s) | 209ms
#> ⠙ 31 done (144/s) | 215ms
#> ⠹ 32 done (145/s) | 222ms
#> ⠸ 33 done (145/s) | 228ms
#> ⠼ 34 done (145/s) | 235ms
#> ⠴ 35 done (145/s) | 241ms
#> ⠦ 36 done (146/s) | 248ms
#> ⠧ 37 done (146/s) | 254ms
#> ⠇ 38 done (146/s) | 261ms
#> ⠏ 39 done (146/s) | 267ms
#> ⠋ 40 done (147/s) | 274ms
#> ⠙ 41 done (147/s) | 280ms
#> ⠹ 42 done (147/s) | 287ms
#> ⠸ 43 done (147/s) | 293ms
#> ⠼ 44 done (147/s) | 299ms
#> ⠴ 45 done (147/s) | 306ms
#> ⠦ 46 done (148/s) | 312ms
#> ⠧ 47 done (148/s) | 319ms
#> ⠇ 48 done (148/s) | 325ms
#> ⠏ 49 done (148/s) | 332ms
#> ⠋ 50 done (148/s) | 338ms
#> ⠙ 51 done (148/s) | 345ms
#> ⠹ 52 done (148/s) | 351ms
#> ⠸ 53 done (148/s) | 358ms
#> ⠼ 54 done (149/s) | 364ms
#> ⠴ 55 done (149/s) | 371ms
#> ⠦ 56 done (149/s) | 377ms
#> ⠧ 57 done (149/s) | 384ms
#> ⠇ 58 done (149/s) | 390ms
#> ⠏ 59 done (149/s) | 397ms
#> ⠋ 60 done (149/s) | 403ms
#> ⠙ 61 done (149/s) | 410ms
#> ⠹ 62 done (148/s) | 421ms
#> ⠸ 63 done (148/s) | 427ms
#> ⠼ 64 done (148/s) | 434ms
#> ⠴ 65 done (148/s) | 440ms
#> ⠦ 66 done (148/s) | 446ms
#> ⠧ 67 done (148/s) | 453ms
#> ⠇ 68 done (148/s) | 459ms
#> ⠏ 69 done (148/s) | 466ms
#> ⠋ 70 done (148/s) | 472ms
#> ⠙ 71 done (149/s) | 479ms
#> ⠹ 72 done (149/s) | 485ms
#> ⠸ 73 done (149/s) | 492ms
#> ⠼ 74 done (149/s) | 498ms
#> ⠴ 75 done (149/s) | 505ms
#> ⠦ 76 done (149/s) | 511ms
#> ⠧ 77 done (149/s) | 518ms
#> ⠇ 78 done (149/s) | 525ms
#> # A tibble: 1 × 6
#>   expression                       min median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>                    <bch:> <bch:>     <dbl> <bch:byt>    <dbl>
#> 1 cli_progress_update(force = … 6.37ms 6.45ms      154.     198KB     2.03
cli_progress_done()