OpenMP in R on OS X

Editor’s Note: This post was updated on 06/18/2017 to address the changes in R 3.4.0, which allow for more native support of OpenMP in R.

Intro

Lately, I’ve spent the past year or so working with parallelization techniques. In particular, I’ve grown accustom to using OpenMP, which follows a shared memory paradigm that enables parallel computing on single computer by taking advantage of the multiple cores shipped on modern CPUs (more info in the HPC Parallel Talk). However, a lot of the work that I do is done on macOS instead of a Windows or a Linux development computer. This is particularly problematic as macOS does not currently support OpenMP under the default compiler (clang). Thus, when the omp header, #include <omp.h>, is included, the clang compiler screams out the following:

clang: warning: argument unused during compilation: '-fopenmp'
fatal error: 'omp.h' file not found

R and OpenMP Sittin’ in a Tree, First comes love…

Lots of what I do is related to the statistical software R. Thus, imagine my surprise when I looked into R’s support for OpenMP and saw the official R project’s comment on this matter being rather bleak:

There is nothing to say what version of OpenMP is supported: version 3.0 (May 2008) is supported by recent versions of the Linux, Windows and Solaris platforms, but portable packages cannot assume that end users have recent versions. OS X currently uses Apple builds of clang with no OpenMP support.

On a more positive note though, official OpenMP with clang should be arriving sometime in June after WWDC as the Intel funded clang-omp feature has been merged into the main clang branch. Thus, there is hope that in XCode 9 OS X users may once again join the flock of OpenMP disciples. (A year later… XCode 8 goes to XCode 9…)

In the interim, there is a workaround that will enable the use of OpenMP in R with OS X. However, this workaround is only really for personal use and does not excuse a developer from protecting against a compiler’s lack of OpenMP support, which I will say is abnormal in this day and age but still…

Then comes a marriage of OpenMP and macOS…

Depending on your R installation, there are a few different ways to go about installing OpenMP on macOS. Please consult the appropriate section below to obtain the correct installation instructions.

Note: You will need to visit the Common Software section to install the shared components between approaches.

Common Software

One of the common tools we will need to install is the Xcode command-line tools that allow for access to a developer rich environment.

First off, let’s install Xcode’s command-line tools

  • Open the Terminal from /Applications/Utilities/
  • Type the following into Terminal
xcode-select --install
  • This will pop up a window that looks like so:
  • Press “Install”
  • Watch it install…
  • Verify installation by typing into terminal:
git --version

3.0.0 - 3.3.*

Next up, installing homebrew, the missing package manager for OS X

  1. Open the Terminal from /Applications/Utilities/
  2. Type the following into Terminal
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Now, let’s use the package manager to grab gcc and in turn gfortran.

There are many different versions of gcc the homebrew offers. To find one that suits you view:

brew search gcc

Obtaining OpenMP requires having a gcc install that is greater than 4.2. Ideally, one should grab GCC 4.9.3, which offers OpenMP v4.0.

brew install homebrew/versions/gcc49 --without-multilib

Note: the use of --without-multilib is because OpenMP may not work with it being active.

Disabling this feature means the lose of support for multiple architectures and, thus, the inability to compile binaries for different architectures. For our purposes, this is aokay.

Now, we need to take care of writing the correct symbolic links via:

# Handles symlink permission issues
sudo chown -R $(whoami):admin /usr/local

# Writes gcc symlink
brew link --overwrite --force gcc49

# You may need to do:
brew unlink gcc49 && brew link gcc49

Update - 06/18/2017 to the 09/23/2016 Update: On April 2nd, 2017, the homebrew team deprecated the boneyard in PR 314. Previously, only the clang-omp formula was deprecated from the main homebrew repository. Therefore, the previously recommend approach to use clang-omp is no longer possible. Instead, the recommendation is to now download and install llvm, which provides OpenMP directly as the latest version of llvm offered by homebrew is now 4.0.0. Therefore, we are going to install llvm with:

brew install llvm

All done with these house keeping instructions for R < 3.4.0. Wasn’t that easy-peazy?

>= 3.4.0

With the release of R 3.4.0, the R binary for macOS is compiled using a compiler and gfortran binaries that are not included with Xcode. However, unlike previous versions of R, these tools do provide support for OpenMP.

Official gfortran binary download

Before we get too involved in the clang compiler specifics, you will need to download the official gfortran 6.1 build. Downloading and installing gfortran 6.1 allows for Fortran routines to benefit from OpenMP code. The official website where the installer is located is here: https://gcc.gnu.org/wiki/GFortranBinaries#MacOS-11

Note: You will need to download the OS X El Capitan gfortran 6.1 binaries regardless of whether or not you are on macOS Sierra, which presently only offers gfortran 6.3.

Obtaining clang

The R team responsible for the maintaining the macOS R binary has made available a pre-built version of the compiler. There are two options presently to installing the binary: 1. use a .pkg installer that I created or 2. use a bash script.

For those use to using a graphical user interface (GUI) installer, you may wish to stick with it. The installer provides a more secure path manipulation and a smarter handling of a pre-existing ~/.R/Makevars. Instructions for obtaining the installer can be found at the GUI Installer section.

On the otherhand, for those who prefer to know exactly what is going on feel free to use the shell script. Users of this approach should be warned in advance that you will overwrite your ~/.R/Makevars file. Instructions for this are at the bash clang4 Install Script section.

GUI Installer

You can verify that the downloaded installer has the above MD5 hash by opening Terminal and typing:

md5 ~/Downloads/clang4-r.pkg 
  • This assumes that the installer was downloaded into the ~/Downloads folder.

As an added benefit to open source, the code used to create the installer is available! You can view how I built the .pkg here https://github.com/coatless/r-macos-clang. I may or may not release a tutorial later on about how to create a .pkg.

Bash clang4 Install Script

# Download binary
curl -O http://r.research.att.com/libs/clang-4.0.0-darwin15.6-Release.tar.gz
# Extract binary onto root directory
tar fvxz clang-4.0.0-darwin15.6-Release.tar.gz -C /

# Overwrite the ~/.R/Makevars
cat <<- EOF > ~/.R/Makevars
# The following statements are required to use the clang4 binary
CC=/usr/local/clang4/bin/clang
CXX=/usr/local/clang4/bin/clang++
CXX11=$CXX
CXX14=$CXX
CXX17=$CXX
CXX1X=$CXX
LDFLAGS=-L/usr/local/clang4/lib
# End clang4 inclusion statements
EOF

Note: Specific CXX11, CXX14, and CXX17 implicit variables were added in R 3.4.0. Previously, using a different C++ compiler with C++ standard different from C++98 was handled with CXX1X.

All done with these house keeping instructions for R >= 3.4.0. Wasn’t that easy-peazy?

Enabling R to Compile Code with OpenMP on macOS

Now, you can either use clang or gcc to compile code while having access to the <omp.h> header. However, we need to let R know about these new options. To do this, we’ll need to use the knowledge found in Section 6.3.2: macOS packages of the R Installation and Administration to modify the ~/.R/Makevars/ file.

Open the ~/.R/Makevars file on macOS via terminal with:

vim ~/.R/Makevars

Then choose your adventure!

3.0.0 - 3.3.*

OpenMP for R via homebrew clang

In the ~/.R/Makevars, write the following:

CC=/usr/local/opt/llvm/bin/clang
CXX=/usr/local/opt/llvm/bin/clang++
CXX1X=/usr/local/opt/llvm/bin/clang++
LDFLAGS=-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib
CXXFLAGS=-I/usr/local/opt/llvm/include
FLIBS=-L/usr/local/Cellar/gcc/4.9.3/lib/gcc/4.9

What we have just done is set global compile options for R. Specifically the lines break down as follows:

  1. Lines 1-3: set the default compiler for C, C++, and C++11 respectively.
  2. Line 4: supplies necessary libc++ library location.
  3. Line 5: supplies include statement.
  4. Line 6: provides a path to using gfortran.

OpenMP for R via homebrew gcc

In the ~/.R/Makevars, write the following:

VER=-4.9 
CC=gcc$(VER)
CXX=g++$(VER)
CXX1X=g++$(VER)
CFLAGS=-mtune=native -g -O2 -Wall -pedantic -Wconversion
CXXFLAGS=-mtune=native -g -O2 -Wall -pedantic -Wconversion
FLIBS=-L/usr/local/Cellar/gcc/4.9.3/lib/gcc/4.9

The above follows slightly from the previous. In this case, I define a VER variable to store the present version of gcc binary. In this case, the addition of the version enables the right bin file to be located in /usr/local/Cellar/gcc/4.9.3/bin. Otherwise, the Xcode version of gcc that maps to clang will be used.

>= 3.4.0

Verify the following statements are in the ~/.R/Makevars file:

CC=/usr/local/clang4/bin/clang
CXX=/usr/local/clang4/bin/clang++
CXX11=$CXX
CXX14=$CXX
CXX17=$CXX
CXX1X=$CXX
LDFLAGS=-L/usr/local/clang4/lib

If they are not present, please add them by pressing I to enter insert mode.

Close the file with ESC + :q

Protect your Users!

Now that you have OpenMP on macOS, you may think…

“Well, everyone now has OpenMP!”

This is not true at all since the entirety of this post exist because most user do not have OpenMP! It is your responsibility to use this new power you gained appropriately. Thus, when writing code using OpenMP please make sure to protect any reference to OpenMP.

The most common instance of this will be the protection of the omp header inclusion. To do so, use the following:

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

Here we use a preprocessor statement #ifdef that checks to see if a macro variable _OPENMP has been defined via #define _OPENMP. If it is defined, then it allows the header file to be read like a traditional if-else. Otherwise, nothing happens.

Note:
Within R 3.4.0, the SUPPORT_OPENMP variable has been removed from Rconfig.h in favor of _OPENMP. Within R 3.2.4, SUPPORT_OPENMP variable was deprecated with the recommended usage being _OPENMP.

The second area that may be problematic is when one goes to parallelize portions of code. In those cases, we need to add an #else statement to the #ifdef to provide serial instructions. Thus, we have:

#ifdef _OPENMP
    // multithreaded OpenMP version of code
#else
    // single-threaded version of code
#endif

Again, the above is to protect primarily users on macOS from not being able to run parallelization code. However, it also protects users that lack an OpenMP compliant compiler. Whenever Apple gets around to enabling OpenMP, this will not be as imperative as it was once before. But, until then, it’s sorta like we’re stuck supporting IE6 even though Microsoft Edge is out.

Misc

This post sprouted out of discussion on the Rcpp-devel listserv and due to questions arising on StackOverflow

comments powered by Disqus