santhisenan
santhisenan

Reputation: 122

How to reduce build time for a Docker container installing R libraries?

I need to run some code that contains both Python 3.8 and R 4.1.0 in a Docker container. Below is my Dockerfile.


FROM python:3.8-slim-buster AS final-image

# R version to install
ARG R_BASE_VERSION=4.1.0

ARG PREBUILD_DEPS="software-properties-common gnupg2"
ARG BUILD_DEPS="build-essential binutils cmake gfortran libblas-dev liblapack-dev libjpeg-dev libpng-dev libnlopt-dev pkg-config"
ARG RUNTIME_DEPS="r-base=${R_BASE_VERSION}-* libcurl4-openssl-dev libssl-dev libxml2-dev"
# venv path
ENV PATH="/opt/venv/bin:$PATH"

RUN apt-get update \
    # Adding this to install latest versions of g++
    && echo 'deb http://deb.debian.org/debian testing main' > /etc/apt/sources.list.d/testing.list \
    # Install the below packages to add repo which is then used to install R version 4
    && apt-get install -y --no-install-recommends $PREBUILD_DEPS \
    && add-apt-repository 'deb http://cloud.r-project.org/bin/linux/debian buster-cran40/'\
    # This key is required to install r-base version 4
    && apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-key FCAE2A0E115C3D8A \
    # Update again to use the newly added sources
    && apt-get update \
    && apt-get install -y --no-install-recommends $RUNTIME_DEPS $BUILD_DEPS \
    && python -m venv /opt/venv \
    && /opt/venv/bin/python -m pip install --upgrade pip \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt packages.R /

RUN pip install wheel setuptools \
    && pip install --no-cache-dir -r requirements.txt \
    && pip install --no-cache-dir --no-binary xgboost xgboost \
    && Rscript packages.R \
    && strip --strip-unneeded usr/local/lib/R/site-library/Boom/lib/libboom.a \
    && strip --strip-debug /usr/local/lib/R/site-library/*/libs/*so \
    # Uninstall unnecessary dependencies
    && rm -rf /tmp/* \
    && apt-get purge -y --auto-remove $BUILD_DEPS $PREBUILD_DEPS \
    && apt-get autoremove -y \
    && apt-get autoclean -y \
    && rm -rf /var/lib/apt/lists/*

ENTRYPOINT XXX

This is packages.R file:

#Setting environment
rm(list = ls())
cat("\014")
print(Sys.time())

# CRAN mirror to use. cran.rstudio.com is a CDN and the recommended mirror.
# Specifying multiple backup CRAN mirrors as Jenkins builds fails
# intermittently due to unavailability of packages in main mirror.
cran_repos = c(MAIN_CRAN_MIRROR = 'https://cran.rstudio.com',
               ALT_CRAN_MIRROR = 'http://cran.r-project.org/')

#Loading Libraries
package_ls <- c(
  "config",
  "crayon",
  "aws.s3",
  "aws.ec2metadata",
  "dplyr",
  "data.table",
  "imputeTS",
  "Metrics",
  "StatMeasures",
  "tseries",
  "purrr",
  "log4r",
  "lubridate",
  "forecast",
  "caret",
  "MASS",
  "stringr",
  "tidyr",
  "uroot",
  "readr",
  "Boruta",
  "bsts"
)

for (pkg_name in package_ls) {
  message("Installing ", pkg_name)
  install.packages(pkg_name, repos = cran_repos)
  if (!(pkg_name %in% installed.packages()[, 'Package'])) {
    stop(pkg_name,
         " is a required package and it could not be installed, stopping!")
  }
}

The Problem

Building the docker container takes a lot more time than I would like. It is because, some packages (e.g. bsts) needs their dependencies (e.g. the C++ library Boom) to be built from source and this is taking a lot of time. Is there a way to either:

  1. Speed up the building of R libraries? OR
  2. Build R libraries in local and copy only the binaries to the Docker container. OR
  3. Reduce the build time in any other way for R packages.

Thanks in advance.

Update

Some ideas from the comments:

By @botje

  1. Install R packages in parallel using the Ncpus argument of install.packages R function. (I have 4 CPUs to work with and setting Ncpus = 4 resulted in a 10% speed up.)
install.packages(package_ls, repos = cran_repos, Ncpus = 4)
  1. Create a custom CRAN mirror containing locally-compiled packages for faster installation.

Upvotes: 4

Views: 1479

Answers (1)

Botje
Botje

Reputation: 31020

I rewrote the last bit of your packages.R as follows:

install.packages(package_ls, Ncpus=16)

This gave me a 3x speed improvement over a run with Ncpus=1 (189s vs 719s).

Upvotes: 3

Related Questions