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 within the SMAC Group is done on OS X instead of a windows or a linux dev box. This is particularly problematic as OS X 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 8 OS X users may once again join the flock of OpenMP disciples.

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 OS X…

To install OpenMP on OS X there are a few house keeping items we need to do first. Chiefly, install Xcode’s command-line tools, homebrew, gcc, and clang-omp.

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

  1. Open the Terminal from /Applications/Utilities/
  2. Type the following into Terminal
xcode-select --install
  1. Press “Install”
  2. Verify installation by typing into terminal:
gcc --version

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 - 09/23/2016: As of July 15th, 2016, homebrew has removed clang-omp from the traditional package repository. The reason for the removal is because OpenMP is now being directly supported by LLVM, which can be obtained via homebrew as well. Therefore, we are going to install clang-omp through:

brew install homebrew/boneyard/clang-omp

Outdated clang-omp command:

brew install clang-omp

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

Enabling R to Compile Code with OpenMP on OS X

Now, you can either use clang-omp 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 OS X packages the R-admin guide to modify the ~/.R/Makevars/ file.

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

vim ~/.R/Makevars

OpenMP for R via clang-omp

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

CC=clang-omp
CXX=clang-omp++
CXX1X=clang-omp++
CXXFLAGS=-I/usr/local/lib
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

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: fills in the missing header information that clang-omp has by using the clang headers compiler.
  3. Lines 5-6: Some useful compile option flags.
  4. Line 7: provides a path to using gfortran.

OpenMP for R via 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.

Protect your Users!

Now that you have OpenMP on OS X, 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: In R 3.2.4, the SUPPORT_OPENMP variable has been deprecated in favor of the default _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 OS X 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