A Short Introduction to rcpptimer

This package provides a simple timer for Rcpp code. The interface resembles the tictoc R package. The package wraps cpptimer, a header-only library that contains a class called CppTimer. rcpptimer adds this class as Timer to the Rcpp namespace.

This introduction explains how to use Rcpp::Timer with Rcpp::cppFunction and how:

  • You can use multiple (potentially nested) timers
  • You can time scopes using Rcpp::Timer::ScopedTimer
  • You can turn off Warnings

Check out the other vignettes for:

  • Using rcpptimer together with Rcpp::sourceCpp vignette("sourceCpp")
  • Adding rcpptimer to a Package vignette("packages")
  • Automatic and Manual Return of the Timings vignette("autoreturn")
  • Accessing unprocessed Timings, Resetting and Updating the Timer Results vignette("advanced")

Initialize a Timer

Initializing a timer is simple. There are four constructors available. The default constructor initializes a timer with warnings enabled that will write the results as data.frame called “times” to the R environment:

Rcpp::Timer timer; // default constructor
Rcpp::Timer timer("my_timer"); // Set a custom name for the results
Rcpp::Timer timer(false); // Disable warnings
Rcpp::Timer timer("my_timer", false); // Set a custom name and disable warnings

Below and throughout other vignettes, we will use all four as needed.

Straightforward Example

With Rcpp::cppFunction, we must add the depends argument to the function to tell the compiler we want to link the ‘rcpptimer’ library to the C++ code. Then, we can construct an instance of the Timer class and use the tic and toc methods to measure the time it takes to execute a code block. Here, we allocate some memory to have something to measure:

Rcpp::cppFunction("
double add(double &x, double &y)
{
 Rcpp::Timer timer;
 timer.tic();
 double z = x + y;
 timer.toc();
 return(z);
}",
  depends = "rcpptimer"
)

add(rnorm(1), runif(1))
#> [1] 0.4024311

This function will automatically write a data frame called “times” to the R environment. Read more about that autoreturn feature (i.e., how to assign a custom variable name and how to manually handle the results) in vignette("autoreturn").

The resulting times object has two classes: data.frame and rcpptimer. We provide a custom S3 method for printing the results. If it is registered, it may scale the results to improve readability (see rcpptimer::print.rcpptimer). Otherwise, it will print the results using base::print.data.frame.

print(times)
#>        Microseconds SD   Min   Max Count
#> tictoc        1.313  0 1.313 1.313     1

Multiple Timers

You can also use multiple timers in the same function. The Timers can be nested and overlapping. Just pass a string to the tic and toc methods to distinguish the timers:

Rcpp::cppFunction('
double add(double &x, double &y)
{
 Rcpp::Timer timer;
 timer.tic("body");
 timer.tic("add_1");
 timer.tic("add_2");
 double z = x + y;
 timer.toc("add_1");
 timer.toc("add_2");
 timer.toc("body");
 return(z);
}',
  depends = "rcpptimer"
)

add(rnorm(1), runif(1))
#> [1] 0.08642267

print(times)
#>       Microseconds SD   Min   Max Count
#> add_1        1.012  0 1.012 1.012     1
#> add_2        1.423  0 1.423 1.423     1
#> body         3.587  0 3.587 3.587     1

rcpptimer will group multiple timers with the same name and calculate summary statistics for them. Consider this more advanced example, which also uses OpenMP:

// fibonacci.cpp
std::vector<long int> fibonacci_omp(std::vector<long int> n)
{

  Rcpp::Timer timer;

  // This scoped timer measures the total execution time of 'fibonacci'
  Rcpp::Timer::ScopedTimer scpdtmr(timer, "fib_body");

  std::vector<long int> results = n;

#pragma omp parallel for
  for (unsigned int i = 0; i < n.size(); ++i)
  {
    timer.tic("fib_" + std::to_string(n[i]));
    results[i] = fib(n[i]);
    timer.toc("fib_" + std::to_string(n[i]));
  }

  return (results);
}

This function is included in rcpptimer, so we can execute it right away:

results <- rcpptimer::fibonacci_omp(n = rep(20:25, 10))
print(times)
#>          Microseconds     SD      Min      Max Count
#> fib_20         43.059 23.849   30.096   97.673    10
#> fib_21         43.835  3.188   41.497   49.873    10
#> fib_22         81.222  7.001   76.182   94.706    10
#> fib_23        124.456 21.960  108.021  169.917    10
#> fib_24        236.084 57.406  194.563  386.021    10
#> fib_25        295.004 25.012  266.728  342.099    10
#> fib_body     2744.623  0.000 2744.623 2744.623     1

Timing Scopes with Rcpp::Timer::ScopedTimer

The ScopedTimer lets you measure the execution time of scopes. It will call ..tic() upon creation and .toc() upon destruction. Consider the simple example below:

Rcpp::cppFunction('
double add(double &x, double &y)
{
 Rcpp::Timer timer;
 Rcpp::Timer::ScopedTimer scoped_timer(timer, "add");
 double z = x + y;
 return(z);
}',
  depends = "rcpptimer"
)

add(rnorm(1), runif(1))
#> [1] 0.3626064

print(times)
#>     Microseconds SD   Min   Max Count
#> add        1.031  0 1.031 1.031     1

Note that you only need to initialize the ScopedTimer. Once it goes out of scope, the timer will automatically be stopped.

Warnings and how to Disable them

The default setting will warn about timers that have been started using .tic but never stopped using .toc() and vice versa. This is useful to catch unmatched .tic() and .toc() calls that may be unmatched due to missing statements or typos.

For example, the following code will produce two warnings:

Rcpp::cppFunction('
double add(double &x, double &y)
{
 Rcpp::Timer timer;
 Rcpp::Timer::ScopedTimer scoped_timer(timer, "add");
 timer.tic("add");
 double z = x + y;
 timer.toc("ad");
 return(z);
}',
  depends = "rcpptimer"
)

add(rnorm(1), runif(1))
#> Warning in add(rnorm(1), runif(1)): Timer "ad" not started yet. 
#> Use tic("ad") to start the timer.
#> [1] -0.08060874

Note that this does not affect terminated timers such as ‘mem’.

print(times)
#>     Nanoseconds SD Min Max Count
#> add         451  0 451 451     1

These warnings occur at runtime. Unfortunately, we can’t check for this at compile time.

However, you can turn off these warnings by passing false to the constructor. This is useful if you need .toc() calls in code blocks that may not get executed, e.g. in conditional statements. The example below will not produce any warnings:

Rcpp::cppFunction('
double add(double &x, double &y)
{
 Rcpp::Timer timer(false);
 Rcpp::Timer::ScopedTimer scoped_timer(timer, "add");
 timer.tic("add");
 double z = x + y;
 timer.toc("ad");
 return(z);
}',
  depends = "rcpptimer"
)

add(rnorm(1), runif(1))
#> [1] -0.4007001

print(times)
#>     Nanoseconds SD Min Max Count
#> add         782  0 782 782     1