Aan de slagGa gratis aan de slag

Verander de grootte van Rcpp-vectors niet

Rcpp-vectorclasses zijn ontworpen als heel dunne wrappers rond R-vectors. Dat heeft als prestatiegevolg dat ze laten groeien of krimpen betekent dat er een nieuwe vector met de juiste grootte wordt aangemaakt en de relevante data wordt gekopieerd. Dat is erg traag en moet je dus vermijden.

Als het kan, structureer je je code zo dat je eerst de uiteindelijke grootte van de vector bepaalt en deze daarna met die grootte alloceert.

Laten we een voorbeeld bekijken dat de positieve waarden uit een vector selecteert, gelijk aan x[x > 0] in R. Omdat je niet van tevoren weet hoeveel positieve getallen er zullen zijn, is het verleidelijk om te beginnen met een vector van lengte nul en elke keer dat je er een vindt, een waarde toe te voegen. Hier is push_back() een functie die een waarde achteraan toevoegt.

NumericVector bad_select_positive_values_cpp(NumericVector x) {
  NumericVector positive_x(0);
  for(int i = 0; i < x.size(); i++) {
    if(x[i] > 0) {
      positive_x.push_back(x[i]);
    }
  }
  return positive_x;
}

Helaas is deze functie traag omdat er herhaaldelijk nieuwe vectors moeten worden gemaakt en de data gekopieerd moet worden. Kijk of jij het beter kunt doen!

Deze oefening maakt deel uit van de cursus

R-code optimaliseren met Rcpp

Cursus bekijken

Oefeninstructies

  • Maak de definitie af van een efficiëntere functie, good_select_positive_values_cpp(), om de positieve getallen te selecteren.
    • In de eerste for-lus: als het i-de element van x groter is dan nul, tel dan één op bij n_positive_elements.
    • Alloceer na die for-lus een numerieke vector, positive_x, met grootte n_positive_elements.
    • Controleer in de tweede for-lus opnieuw of het i-de element van x groter is dan nul.
    • Als dat zo is, zet dan het j-de element van positive_x gelijk aan het i-de element van x en tel daarna één op bij j.
  • bad_select_positive_values_cpp() is beschikbaar in je werkruimte ter vergelijking. Bekijk de console-uitvoer om het gebenchmarkt verschil in looptijd te zien.

Praktische interactieve oefening

Probeer deze oefening eens door deze voorbeeldcode in te vullen.

#include 
using namespace Rcpp;

// [[Rcpp::export]]
NumericVector good_select_positive_values_cpp(NumericVector x) {
  int n_elements = x.size();
  int n_positive_elements = 0;
  
  // Calculate the size of the output
  for(int i = 0; i < n_elements; i++) {
    // If the ith element of x is positive
    if(___) {
      // Add 1 to n_positive_elements
      ___;
    }
  }
  
  // Allocate a vector of size n_positive_elements
  ___;
  
  // Fill the vector
  int j = 0;
  for(int i = 0; i < n_elements; i++) {
    // If the ith element of x is positive
    if(___) {
      // Set the jth element of positive_x to the ith element of x
      ___;
      // Add 1 to j
      ___;
    }
  }
  return positive_x;
}

/*** R
set.seed(42)
x <- rnorm(1e4)
# Does it give the same answer as R?
all.equal(good_select_positive_values_cpp(x), x[x > 0])
# Which is faster?
microbenchmark(
  bad_cpp = bad_select_positive_values_cpp(x),
  good_cpp = good_select_positive_values_cpp(x)
)
*/
Code bewerken en uitvoeren