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
- Open the
Terminal
from/Applications/Utilities/
- 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
- Download the clang4-r.pkg (263.6 mb) from https://uofi.box.com/v/r-macos-clang-pkg
- MD5 Hash: f49df42ccc84ec529c489e8e3f02248c
- Install it!
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:
- Lines 1-3: set the default compiler for C, C++, and C++11 respectively.
- Line 4: supplies necessary libc++ library location.
- Line 5: supplies include statement.
- 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