Compiler optimization
Motivation
R uses default GCC/Clang compiler optimizations, which can limit the performance of C++ code.
User-wide Makevars
The following Makevars example, which should be in ~/.R/Makevars
, SETS the optimization level to -O3
for all C++ code compiled by R and uses additional flags to enable more aggressive optimizations that can be set at setup level but not package level (or CRAN will reject the package):
# Override R's optimization level
CXXFLAGS = -g -O3 -march=native
CXX11FLAGS = -g -O3 -march=native
CXX14FLAGS = -g -O3 -march=native
CXX17FLAGS = -g -O3 -march=native
# Additional optimizations
CXXFLAGS += -funroll-loops -ftree-vectorize -fprefetch-loop-arrays
CXXFLAGS += -fomit-frame-pointer -fstrict-aliasing
# Link-time optimization
CXXFLAGS += -flto=auto
LDFLAGS += -flto=auto
Anticonf scripts
The idea of an “anticonf” configure script is to provide a tailored Makevars file having in mind that a package will be run on different systems. The anticonf name comes from the fact that it is not a standard configure
script created with tools such as GNU Autoconf, but rather a templated script that generates a Makevars
by detecting the system and compiler settings (e.g., number of cores, optimization flags, etc.).
The capybara package has the following Makevars.in
file:
CXX_STD ?= CXX11 CXX14 CXX17
PKG_CXXFLAGS = -DARMA_NO_DEBUG -DARMA_USE_BLAS -DARMA_USE_LAPACK -DCAPYBARA_NCORES=@ncores@ $(SHLIB_OPENMP_CXXFLAGS) @SAFE_OPTFLAGS@
PKG_LIBS = $(SHLIB_OPENMP_CXXFLAGS) $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS)
This file is a template for the Makevars
file. The @ncores@
and @SAFE_OPTFLAGS@
are replaced by the configure
script, which is run when the package is installed.
Capybara’s configure
script is as follows:
# Anticonf script by Pacha (2025)
PKG_CONFIG_NAME="capybara"
# Get R configuration
CXX=$(${R_HOME}/bin/R CMD config CXX)
CXXFLAGS=$(${R_HOME}/bin/R CMD config CXXFLAGS)
# Function to test compiler flag
test_flag() {
echo 'int main(){return 0;}' > conftest.cpp
if $CXX $CXXFLAGS $1 conftest.cpp -o conftest >/dev/null 2>&1; then
rm -f conftest conftest.cpp
return 0
else
rm -f conftest conftest.cpp
return 1
fi
}
# For CRAN, we can't override -O2, but we can add other safe optimizations
SAFE_OPTFLAGS=""
# Test portable optimization flags that don't change the -O level
PORTABLE_OPTS="-funroll-loops -ftree-vectorize"
for opt in $PORTABLE_OPTS; do
if test_flag "$opt"; then
SAFE_OPTFLAGS="$SAFE_OPTFLAGS $opt"
fi
done
if [ -n "$SAFE_OPTFLAGS" ]; then
echo "Additional optimizations:$SAFE_OPTFLAGS"
fi
# Detect number of cores
if [ -n "$CAPYBARA_NCORES" ]; then
num_cores="$CAPYBARA_NCORES"
else
if [ -f /proc/cpuinfo ]; then
num_cores=$(grep -c "^processor" /proc/cpuinfo 2>/dev/null || echo 1)
elif [ "$(uname)" = "Darwin" ]; then
num_cores=$(sysctl -n hw.ncpu 2>/dev/null || echo 1)
else
num_cores=1
fi
if [ "$num_cores" -gt 2 ]; then
num_cores=$((num_cores - 1))
fi
fi
echo "Default thread count: $num_cores"
# Create Makevars from template
echo "Creating src/Makevars"
sed -e "s|@ncores@|${num_cores}|g" \
-e "s|@SAFE_OPTFLAGS@|${SAFE_OPTFLAGS}|g" \
> src/Makevars
src/Makevars.in
echo "Configuration complete"
echo ""
echo "NOTE: This build will use R's default -O2 optimization level for CRAN compliance."
echo "For maximum performance, see inst/Makevars.user.example"
exit 0
This script detects the number of cores available on the system and sets the CAPYBARA_NCORES
variable accordingly. It also tests for safe optimization flags that can be added to the Makevars
file without changing the default -O2
optimization level used by R. It also serves the goal of testing that a simple file can be compiled.