rcpptimer: Rcpp Tic-Toc Timer with OpenMP Support

Jonathan Berrisch

University of Duisburg-Essen, House of Energy Markets and Finance

2024-07-11

How it started

I searched for a benchmarking tool for my (R)cpp code

I found RcppClock

RcppClock was slow, so I started working on it

But why is it so cool?

It’s simple:

Rcpp::Clock clock;   // Creates clock instance

clock.tick("my_clock");
// Some code I want to benchmark
clock.tock("my_clock");

clock.stop();       // Passes results to R

Improvements RcppTimer

Speed

Using std::map to match .tick() and .tock() calls is efficient:

std::map<std::string, tp> tickmap;

// Start timer: write name + time into tickmap
void tick(std::string name)
{
    tickmap.insert(std::pair<std::string, tp>(name, sc::high_resolution_clock::now()));
}

// Stop timer: write duration into timers, save key 
void tock(std::string name)
{
    timers.push_back(
      sc::duration_cast<sc::nanoseconds>(
        sc::high_resolution_clock::now() - tickmap[name]
        ).count()
      );
    keys.push_back(name);
}

Commit

OMP Support

At this stage, RcppTimer can’t handle OpenMP parralelism as it can’t distinguish between threads.

Simple solution: Add Threadnumber to the Key of the tickmap.

using key = std::string;
std::map<key, tp> tickmap;

// Start timer: write name + time into tickmap
void tick(std::string name)
{
    tickmap.insert(std::pair<key, tp>(name, sc::high_resolution_clock::now()));
}

Commit

OMP Support

At this stage, RcppTimer can’t handle OpenMP parralelism as it can’t distinguish between threads.

Simple solution: Add Threadnumber to the Key of the tickmap.

using keypair = std::pair<std::string, int>;
std::map<keypair, tp> tickmap;

// Now key is augmented by the thread number
void tick(std::string name)
{
    keypair key(name, omp_get_thread_num());
    tickmap.insert(std::pair<keypair, tp>(key, sc::high_resolution_clock::now()));
}

Commit

OMP Memory Access

Parallelism can cause memory access problems

But we can “lock” objects in OMP threads before accessing them


using keypair = std::pair<std::string, int>;
std::map<keypair, tp> tickmap;

// Now key is augmented by the thread number
void tick(std::string name)
{
    keypair key(name, omp_get_thread_num());
    
    
    tickmap.insert(std::pair<keypair, tp>(key, sc::high_resolution_clock::now()));
    
}

OMP Memory Access

Parallelism can cause memory access problems

But we can “lock” objects in OMP threads before accessing them


using keypair = std::pair<std::string, int>;
std::map<keypair, tp> tickmap;

// Now key is augmented by the thread number
void tick(std::string name)
{
    keypair key(name, omp_get_thread_num());
    #pragma omp critical
    {
        tickmap.insert(std::pair<keypair, tp>(key, sc::high_resolution_clock::now()));
    }
}

Automatically Return Results to R


Use deconstructor to pass results to R

namespace Rcpp
{
  class Timer : public CppTimer
  {
    // Destroy - pass data to R
    ~Timer()
    {
      if (autoreturn)
        stop();
    }
  }
}

Commit

Wrap-up

Wrap-up

Simple

//[[Rcpp::depends(rcpptimer)]]
#include <rcpptimer.h>

void main(){
  Rcpp::Timer timer;
  timer.tic("First");
  #pragma omp parallel for
  for(int i = 1; i <= 10; i++){
    timer.tic("Second");
    cout << i << endl;
    timer.toc("Second");
  }
  timer.toc("First");
}

Wrap-up

Simple

//[[Rcpp::depends(rcpptimer)]]
#include <rcpptimer.h>

void main(){
  Rcpp::Timer timer;
  timer.tic("First");
  #pragma omp parallel for
  for(int i = 1; i <= 10; i++){
    timer.tic("Second");
    cout << i << endl;
    timer.toc("Second");
  }
  timer.toc("First");
}
[R] times
  Name Milliseconds    SD Count
First         0.002 0.000    1
Second        0.048 0.011    10

Wrap-up

Simple

//[[Rcpp::depends(rcpptimer)]]
#include <rcpptimer.h>

void main(){
  Rcpp::Timer timer;
  timer.tic("First");
  #pragma omp parallel for
  for(int i = 1; i <= 10; i++){
    timer.tic("Second");
    cout << i << endl;
    timer.toc("Second");
  }
  timer.toc("First");
}
[R] times
  Name Milliseconds    SD Count
First         0.002 0.000    1
Second        0.048 0.011    10

Powerful

  • Overlapping timers
  • OMP support
  • Auto-Return to R
  • ScopedTimer
  • Reset method
  • Sanity checks
  • Countless speed optimizations
  • Microseconds resolution
  • On Cran: rcpptimer