Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
R and Shiny Training: If you find this blog to be interesting, please note that I offer personalized and group-based training sessions that may be reserved through Buy me a Coffee. Additionally, I provide training services in the Spanish language and am available to discuss means by which I may contribute to your Shiny project.
< section id="motivation" class="level1">Motivation
One common phrase that I find when I need to Google how to do something with cpp11
is “Don’t use cpp11 because it does not offer OpenMP support.”
This is a myth. cpp11
does offer OpenMP support. In this blog post, I will show you how to use OpenMP with cpp11
, but here I assume your C++ compiler already supports OpenMP.
I tested this on Windows, where you need to install Rtools, and Linux Mint (Ubuntu based) where I didn’t need anything special because the gcc
compiler comes with the operating system and just works. If you are using macOS, you need to install libomp
via Homebrew in order to extend the clang
compiler, and this is explained here.
Creating a package
First, we need to create a package. I will use usethis
to create a package called cpp11omp
:
usethis::create_project("cpp11omp")
Then, I will add cpp11
as a dependency:
usethis::use_cpp11()
As the cpp11
message indicates, I created a file called R/cpp11omp-package.R
with the following contents:
## usethis namespace: start #' @useDynLib cpp11omp, .registration = TRUE ## usethis namespace: end NULL< section id="adding-functions" class="level1">
Adding functions
< section id="cpp11-unnamed-list" class="level2">Cpp11 unnamed list
I added a function called squared_unnamed_
in src/code.cpp
that will square each element in a vector of doubles, so the file content corresponds to the following:
#include <cpp11.hpp> #include <omp.h> using namespace cpp11; [[cpp11::register]] list squared_unnamed_(doubles x) { // create vectors y = x^2 and z = thread number int n = x.size(); writable::doubles y(n); writable::doubles z(n); #pragma omp parallel for for (int i = 0; i < n; ++i) { y[i] = x[i] * x[i]; z[i] = omp_get_thread_num(); } //create a list containing y and z writable::list out; out.push_back(y); out.push_back(z); return out; }
The previous function returns an unnamed list with two elements: the squared vector and the thread number. The function is registered with [[cpp11::register]]
so that it can be called from R.
If I try to run square_unnamed_(1:10)
, it will return an error because I am passing a vector of integers instead of doubles. C++ is strict with types, so I need to create a wrapper function that will convert the integers to doubles, and it will go inside R/cpp11omp-package.R
:
#' Unnamed list with squared numbers and the threads used #' @param x A vector of doubles #' @export squared_unnamed <- function(x) { squared_unnamed_(as.double(x)) }
The previous function is exported with @export
so that it can be called by the end user. The function squared_unnamed_
is an internal function. This approach also has the advantage that I can document the function in a flexible way.
Cpp11 named list
I added a function called squared_named_
in src/code.cpp
that does the same but returns a named list. The additional content corresponds to the following:
[[cpp11::register]] list squared_named_(doubles x) { // create vectors y = x^2 and z = thread number int n = x.size(); writable::doubles y(n); writable::doubles z(n); #pragma omp parallel for for (int i = 0; i < n; ++i) { y[i] = x[i] * x[i]; z[i] = omp_get_thread_num(); } //create a list containing y and z writable::list out; out.push_back({"x^2"_nm = y}); out.push_back({"thread"_nm = z}); return out; }
As in the previous part, I added a wrapper and documentation:
#' Named list with squared numbers and the threads used #' @param x A vector of doubles #' @export squared_named <- function(x) { squared_named_(as.double(x)) }< section id="makevars" class="level2">
Makevars
In order to make the #pragma
instruction work, I need to add the following to src/Makevars
:
PKG_CXXFLAGS = $(SHLIB_OPENMP_CXXFLAGS) PKG_LIBS = $(SHLIB_OPENMP_CXXFLAGS) CXX_STD = CXX11
If I don’t do this, the pragma instruction will be ignored and the functions will run in a single thread.
< section id="building-and-testing" class="level1">Building and testing
I used devtools
to build and test the package:
cpp11::cpp_register() devtools::document() devtools::install()
Then, I tested the package from a new R session:
> library(cpp11omp) > squared_unnamed(1:10) [[1]] [1] 1 4 9 16 25 36 49 64 81 100 [[2]] [1] 0 0 1 1 2 3 4 5 6 7 > squared_named(1:10) $`x^2` [1] 1 4 9 16 25 36 49 64 81 100 $thread [1] 0 0 1 1 2 3 4 5 6 7< section id="complete-code" class="level1">
Complete code
The complete code is available in this GitHub repository.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.