A Short Introduction to rcpptimer
Jonathan Berrisch
2024-09-22
Source:vignettes/rcpptimer.Rmd
rcpptimer.Rmd
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.7992826
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)
#> Nanoseconds SD Min Max Count
#> tictoc 912 0 912 912 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.5396038
print(times)
#> Microseconds SD Min Max Count
#> add_1 1.152 0 1.152 1.152 1
#> add_2 1.183 0 1.183 1.183 1
#> body 3.246 0 3.246 3.246 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 37.402 11.313 26.760 57.398 10
#> fib_21 42.802 10.317 35.607 61.315 10
#> fib_22 84.399 17.852 68.689 118.723 10
#> fib_23 126.667 42.568 93.046 206.148 10
#> fib_24 221.109 55.876 171.192 325.593 10
#> fib_25 290.105 71.028 239.441 417.536 10
#> fib_body 2836.349 0.000 2836.349 2836.349 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.7273107
print(times)
#> Nanoseconds SD Min Max Count
#> add 902 0 902 902 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.9221172
Note that this does not affect terminated timers such as ‘mem’.
print(times)
#> Nanoseconds SD Min Max Count
#> add 461 0 461 461 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] -1.419489
print(times)
#> Nanoseconds SD Min Max Count
#> add 761 0 761 761 1