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.in > src/Makevars

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.

References