Почему преобразование LogicalVector в std::vector<double> занимает так много времени?
При использовании Rcpp для взаимодействия с внешними библиотеками мы должны преобразовать встроенные классы контейнеров Rcpp, чаще всего, в стандартные классы контейнеров. Это преобразование, как всегда, имеет свою стоимость. Как правило, эти издержки являются довольно определенными, но в случае преобразования Rcpp LogicalVector
к std::vector<double>
стоимость, кажется, не соответствует расходам на кастинг / продвижение другого типа...
Рассмотрим равенство следующих шести простых преобразований из LogicalVector в контейнеры std::vector...
// 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
Мы получаем это равенство из-за магии шаблонов Rcpp. Мой шаблон-фу не достаточно силен, чтобы понять, почему существует такая крайняя разница в накладных расходах на ручное преобразование против автоматических преобразований, как показано в этих результатах теста...
> 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!
Сравнивая стоимость использования контейнеров Rcpp напрямую, автоматически конвертируется в стандартные контейнеры аналогичных типов и контейнеры повышенных типов, например, так...
# 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!
... легко увидеть выброс. Если мы попытаемся передать логический вектор как контейнер типа std, а затем позволить C++ выполнить продвижение при создании нового контейнера, результаты не будут лучше...
> 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
Но если LogicalVector
поддерживается Rcpp и используется для непосредственного заполнения стандартного контейнера, результаты намного лучше.
> 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!
Я не могу понять, почему автоматическое преобразование из LogicalVector
в std::vector<double>
так относительно медленно...