r-lib / rlang

Low-level API for programming with R
https://rlang.r-lib.org
Other
500 stars 137 forks source link

Optimize `r_dyn_*_push_back()` for vector types #1542

Closed DavisVaughan closed 1 year ago

DavisVaughan commented 1 year ago

The push-back operation of our dynamic vector is probably one of the most commonly used ops in practice, so it would be nice if this was really fast. Currently it calls into r_dyn_push_back(), which is generic (which is great!) but slow because of its generality.

I was looking into replacing our vctrs growable type with dynamic vectors and was surprised to see just how slow our dynamic push-back was in comparison.

Consider this simple example of doing repeated push-backs and no resizing.

r_obj* ffi_test_push_back() {
  r_ssize size = 100000;

  struct r_dyn_array* p_vec = r_new_dyn_vector(R_TYPE_integer, size);
  KEEP(p_vec->shelter);

  for (r_ssize i = 0; i < size; ++i) {
    r_dyn_int_push_back(p_vec, i);
  }

  r_obj* out = r_dyn_unwrap(p_vec);

  FREE(1);
  return out;
}
# CRAN rlang
bench::mark(.Call(ffi_test_push_back), iterations = 10000)
#> # A tibble: 1 × 6
#>   expression                     min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>                <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 .Call(ffi_test_push_back)    503µs    610µs     1609.     391KB     11.7

# This PR
bench::mark(.Call(ffi_test_push_back), iterations = 10000)
#> # A tibble: 1 × 6
#>   expression                     min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>                <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 .Call(ffi_test_push_back)    113µs    165µs     5699.     391KB     41.3

Providing "native" push-back operators is a big win here, so it definitely seems worth it to me.

DavisVaughan commented 1 year ago

-O2 and on an Intel mac