Advanced Usage of rcpptimer

Jonathan Berrisch

2024-09-21

Accessing Unprocessed Data

The stop() method returns the aggregated data as a DataFrame. However, you can also access the raw data if you need to. The timer class stores the timings in two vectors: durations and tags, both public class members. The snippet below demonstrates how to access them:

Rcpp::cppFunction('
DataFrame demo_rnorm()
{
  Rcpp::Timer timer;
  double x=0;
  for(int i = 0; i < 7; i++)
  {
    timer.tic("rnorm");
    x += rnorm(1, 1)[0];
    timer.toc("rnorm");
  }
  DataFrame times = DataFrame::create(
          Named("Durations") = timer.durations,
          Named("Tags") = timer.tags);
  return(times);
}',
  depends = "rcpptimer"
)

demo_rnorm()
##   Durations  Tags
## 1      6767 rnorm
## 2       205 rnorm
## 3        89 rnorm
## 4       162 rnorm
## 5       149 rnorm
## 6        88 rnorm
## 7        86 rnorm

You can see that the tags vector contains the names of the timings, and the durations vector contains the actual timings (in nanoseconds).

Updating the Results

Sometimes, you may want to access the results of your Timer instance before all timers have finished. This is possible. Due to the way the Timer class processes the data, it is also very efficient. The .aggregate() method (called by .stop()) will update the results if new timings have been observed. The snippet below demonstrates this:

List test_update()
{
  Rcpp::Timer timer;
  timer.autoreturn = false;
  List L = List::create();
  {
    Rcpp::Timer::ScopedTimer scoped_timer(timer, "t1");
    timer.tic("t2");
    std::this_thread::sleep_for(std::chrono::nanoseconds(5));
    timer.toc("t2");
    DataFrame results1 = timer.stop();
    timer.print_warnings();
    L.push_back(results1);
    timer.tic("t2");
    std::this_thread::sleep_for(std::chrono::nanoseconds(500));
    timer.toc("t2");
    timer.tic("t3");
    std::this_thread::sleep_for(std::chrono::nanoseconds(500));
    timer.toc("t3");
  }
  DataFrame results2 = timer.stop();
  L.push_back(results2);
  return (L);
}

We use the above function in rcpptimer for testing purposes:

rcpptimer:::test_update()
## Warning in rcpptimer:::test_update(): Timer "t1" not stopped yet. 
## Use toc("t1") to stop the timer.
## [[1]]
##    Microseconds SD    Min    Max Count
## t2       54.481  0 54.481 54.481     1
## 
## [[2]]
##    Microseconds    SD     Min     Max Count
## t1      572.870 0.000 572.870 572.870     1
## t2       54.233 0.351  53.985  54.481     2
## t3       52.186 0.000  52.186  52.186     1

You can see that the timer “t2” has been updated with the new timing. This example also shows that it is unnecessary to stop all timers before calling .stop(). In this example, timer “t1” is started before calculating the results for the first time, but it is stopped before calling .stop() a second time.

Also, note that we need to manually call .print_warnings() if we want to print warnings early. Otherwise, they will only be printed upon destruction of the Timer instance (e.g., when your Timer object goes out of scope).

Resetting the Timer

You can reset the timer at any time by calling the reset() method. This method will clear your instance of the Timer class and reset the internal state. The example below demonstrates how to use it:

List test_reset()
{
  Rcpp::Timer timer;
  {
    Rcpp::Timer::ScopedTimer scoped_timer(timer, "t1");
    timer.autoreturn = false;
    timer.tic("t2");
    std::this_thread::sleep_for(std::chrono::nanoseconds(5));
    timer.toc("t2");
  }
  DataFrame results1 = timer.stop();
  timer.reset();
  timer.tic("t3");
  List L = List::create();
  L.push_back(results1);
  timer.toc("t3");
  DataFrame results2 = timer.stop();
  L.push_back(results2);
  return (L);
}

We use the above function in rcpptimer for testing purposes:

rcpptimer:::test_reset()
## [[1]]
##    Microseconds SD    Min    Max Count
## t1       58.640  0 58.640 58.640     1
## t2       57.427  0 57.427 57.427     1
## 
## [[2]]
##    Nanoseconds SD Min Max Count
## t3         530  0 530 530     1