3

When using Rcpp to interface with external libraries we must convert from the Rcpp built-in container classes to, most commonly, standard container classes. This conversion, as always, has a cost. Typically this overhead is fairly determinate but in the case of Rcpp's conversion of LogicalVector to a std::vector<double> the cost seems out of line with the other type casting/promotion costs...

Consider the equality of the following six simple conversions from LogicalVector to std::vector containers...

// Equality check functions.
// [[Rcpp::export]]
svi test_auto_conversion_std_int(const svi& v) {
  return v;
}
// [[Rcpp::export]]
svi test_as_conversion_std_int(const lv& v) {
  svi v2 = as<svi>(v);
  return v2;
}
// [[Rcpp::export]]
svi test_manual_conversion_std_int(const nv& v) {
  svi v2(v.begin(),v.end());
  return v2;
}
// [[Rcpp::export]]
svd test_auto_conversion_std_dbl(const svd& v) {
  return v;
}
// [[Rcpp::export]]
svd test_as_conversion_std_dbl(const lv& v) {
  svd v2 = as<svd>(v);
  return v2;
}
// [[Rcpp::export]]
svd test_manual_conversion_std_dbl(const nv& v) {
  svd v2(v.begin(),v.end());
  return v2;
}

    > small_l_vec <- c(T,F,NA)
    > small_i_vec <- as.integer(small_l_vec)
    > small_n_vec <- as.numeric(small_l_vec)
    > all( identical(small_i_vec, test_auto_conversion_std_int(small_l_vec)),
    +      identical(small_i_vec, test_as_conversion_std_int(small_l_vec)),
    +    .... [TRUNCATED] 
    [1] TRUE

    > all( identical(small_n_vec, test_auto_conversion_std_dbl(small_l_vec)),
    +      identical(small_n_vec, test_as_conversion_std_dbl(small_l_vec)),
    +    .... [TRUNCATED] 
    [1] TRUE

We get this equality because of Rcpp's templating magic. My template-fu is not strong enough to dive into why there is such an extreme difference in the overhead of manually converting vs the automagic conversions as illustrated in these benchmark results...

    > benchmark_as_tested_for_equality()  # includes copy costs
    Unit: microseconds
                                      expr   median neval
         test_as_conversion_std_int(l_vec) 10121.17   500
     test_manual_conversion_std_int(l_vec) 12545.34   500
       test_auto_conversion_std_int(l_vec) 12654.28   500
     test_manual_conversion_std_dbl(l_vec) 18590.10   500
         test_as_conversion_std_dbl(l_vec) 19653.39   500
       test_auto_conversion_std_dbl(l_vec) 26897.93   500  <<< OUCH!

Comparing the cost of using the Rcpp containers directly, automatically converted to std containers of like types, and containers of promoted types like so...

# Functions return "size" only so no copy costs.

> declared_direct()      > declared_std_like_types()
          expr median               expr   median
rcpp_lv(l_vec) 1.1280    std_bool(l_vec) 7352.500


> declared_promoted_r()  > declared_promoted_std()
          expr   median            expr     median
rcpp_iv(l_vec) 2932.712  std_int(l_vec)   6790.508
rcpp_nv(l_vec) 5359.769  std_dbl(l_vec)  12810.550 <<< OUCH!

... it is easy to see the outlier. If we attempt to pass the logical vector as a like type std container and then let c++ do the promotion when construction the new container the results are no better...

    > declared_std_like_types_promoted_using_std_promotion()
                            expr    median
     std_bool_promote_int(l_vec) 12725.724
     std_bool_promote_dbl(l_vec) 13626.782

But if the LogicalVector is promoted by Rcpp and that is used to populate a standard container directly the results are much better.

 > declared_promoted_r_to_std_like_type()
                            expr   median
  rcpp_lv_promote_std_int(l_vec) 5019.586
  rcpp_lv_promote_std_dbl(l_vec) 8007.522 <<< Much better!

What I can't figure out is why the automatic conversion from LogicalVector to std::vector<double> is so relatively slow...

Thell
  • 5,883
  • 31
  • 55

0 Answers0