Using OpenMP with a C Routine and Rcpp

Parallelizing a C routine with OpenMP and surfacing it through Rcpp

programming
cpp
Author

James Balamuta

Published

January 5, 2019

This post is a version of the rcpp-c-and-openmp README from the R&D Rcpp collection of compiled-code examples. The repository holds the full, runnable source.

This small example R package demonstrates how a C routine parallelized with OpenMP can be compiled inside an R package and then surfaced in R through a thin Rcpp wrapper. It is one of the “R&D Rcpp” prototype packages, a collection of minimal examples for integrating compiled code with R. Each package isolates a single integration technique so the moving parts stay easy to follow.

The RcppCandOpenMP R package provides an example of parallelizing a C routine with OpenMP and surfacing it in R through C++ code with Rcpp.

Usage

To install the package, you must first have a compiler on your system that is compatible with R and that supports OpenMP. For help on obtaining a compiler consult either macOS or Windows guides.

With a compiler in hand, one can then install the package from GitHub by:

# install.packages("remotes")
remotes::install_github("coatless-rd-rcpp/rcpp-c-and-openmp")
library("RcppCandOpenMP")

Implementation Details

The goal of this project is to show how a C routine that is parallelized with OpenMP can be compiled inside an R package and then exposed to R through a thin Rcpp wrapper. There are three moving parts: the parallel C routine itself, the Makevars files that turn OpenMP on in a portable way, and the Rcpp function that hands R’s data to the C routine.

The example routine walks over a numeric vector and multiplies every element by two, distributing the loop iterations across the available OpenMP threads.

.
├── DESCRIPTION                         # Package metadata
├── NAMESPACE                           # Function and dependency registration
├── R                                   # R functions
   ├── RcppCandOpenMP-package.R        # Package documentation
   └── RcppExports.R                   # Autogenerated R to C++ bindings by Rcpp
├── README.md
├── man                                 # Package documentation
   ├── multiply_by_two.Rd
   └── RcppCandOpenMP-package.Rd
└── src                                 # Compiled code
    ├── Makevars                        # Enable OpenMP on Unix-alikes (macOS/Linux)
    ├── Makevars.win                    # Enable OpenMP on Windows
    ├── my_c_parallel_func.c            # The OpenMP-parallelized C routine
    ├── surface_c_fun_with_rcpp.cpp     # Rcpp wrapper that calls the C routine
    └── RcppExports.cpp                 # Autogenerated R bindings

The Parallel C Routine

The C routine lives in src/my_c_parallel_func.c. The <omp.h> header is included only when the compiler advertises OpenMP support through the _OPENMP macro, which keeps the routine compiling cleanly on toolchains that lack OpenMP. The #pragma omp parallel for directive splits the for loop across threads; firstprivate(x, n) gives each thread its own copy of the pointer and the iteration count.

#include <R.h>
#include <Rinternals.h>

#ifdef _OPENMP
#include <omp.h>
#endif

void parallel_function(double *x, int n) {
  int i;
  #pragma omp parallel for firstprivate(x, n)
  for (i = 0; i < n; i++){ x[i] *= 2; }
}

Here n is the number of elements of x to process: it is the upper bound of the loop, and those n iterations are what OpenMP divides among the threads. The number of threads themselves is governed by the OpenMP runtime (for example, the OMP_NUM_THREADS environment variable), not by this argument.

Enabling OpenMP with Makevars

OpenMP is enabled through src/Makevars (Unix-alikes) and src/Makevars.win (Windows). Rather than hard-coding compiler flags such as -fopenmp, both files use R’s $(SHLIB_OPENMP_*FLAGS) variables, which expand to the correct OpenMP flag for whichever compiler R was built with.

PKG_CFLAGS = $(SHLIB_OPENMP_CFLAGS)
PKG_CXXFLAGS = $(SHLIB_OPENMP_CXXFLAGS)
PKG_LIBS = $(SHLIB_OPENMP_CXXFLAGS)

Because the parallel #pragma lives in a .c file, the OpenMP flag has to reach the C compiler, which is what PKG_CFLAGS = $(SHLIB_OPENMP_CFLAGS) ensures. Setting only PKG_CXXFLAGS (the C++ flag) would leave my_c_parallel_func.c compiled without OpenMP, so the #pragma would be ignored and the loop would run serially. PKG_LIBS links the OpenMP runtime; using $(SHLIB_OPENMP_CXXFLAGS) there is appropriate because the shared object is linked with the C++ driver. Using these variables, rather than literal flags, is the portable, CRAN-friendly way to request OpenMP.

Surfacing the Routine with Rcpp

The bridge to R lives in src/surface_c_fun_with_rcpp.cpp. Because parallel_function() is compiled as C, it is declared inside an extern "C" block so the C++ compiler resolves it with C linkage. The exported multiply_by_two() function passes the raw double* buffer underlying the Rcpp::NumericVector (obtained with x.begin()) straight to the C routine, together with the element count n. The // [[Rcpp::export]] attribute generates the R-level binding.

#include <Rcpp.h>

// Ensure _C++_ can access the function.
extern "C" {
  void parallel_function(double *x, int n);
}

// [[Rcpp::export]]
void multiply_by_two(Rcpp::NumericVector x, int n) {
   parallel_function( x.begin(), n );
}

The exported function also carries //' roxygen comments (trimmed from the excerpt here); Rcpp::compileAttributes() copies these into R/RcppExports.R, which is where the package’s R help page for multiply_by_two() is generated from. From R the routine is then called as:

# Data
x = 1:5

# Process all elements of x
n = length(x)

# Call the routine
multiply_by_two(x, n = n)

Source

The full package source is available on GitHub, and the rendered package site can be found at rd-rcpp.thecoatlessprofessor.com/rcpp-c-and-openmp.