Simon Barthelmé (GIPSA-lab, CNRS)

There are several ways of doing things in parallel with imager. One of them is to use of R’s many packages for doing things in parallel (parallel, futures, etc.). The other one is to take advantage of CImg’s ative use of OpenMP.

1 Parallelising from R

Parallelising from R is very easy (provided that what you want to do actually parallelises). Something that does parallelise easily is to run the same operations on different images, or on different image channels.

library(parallel)

#A really big image
im <- boats %>% imresize(8)
#Rank pixels in each image channel 
#Serial version 
fun <- function() imsplit(im,"c") %>% lapply(rank)
    
system.time(fun())
##    user  system elapsed 
##   7.420   0.148   7.569
#Parallel version: use mclapply
fun.par <- function() imsplit(im,"c") %>% mclapply(rank,mc.cores=2)
system.time(fun.par())
##    user  system elapsed 
##   2.608   0.200   5.297

2 Native parallelisation: CImg and OpenMP

Many CImg operations are parallelised natively. The parallelisation is optional and is only activated starting from a certain image size. The speed-ups are sublinear, meaning that unless your image is gigantic you won’t gain much from throwing 200 cores at a problem.

By default OpenMP will grab all the CPU cores it can. You can control how many cores are accessed using OpenMPController::omp_set_num_threads:

library(imager)
library(microbenchmark)
#Let's do a big convolution
a <- boats
b <- imnoise(30,30) 
fun <- function() convolve(a,b)
#No parallelisation
OpenMPController::omp_set_num_threads(1)
## [[1]]
## [1] 1
microbenchmark(fun(),times=15)
## Unit: milliseconds
##   expr      min       lq     mean   median       uq      max neval
##  fun() 321.2343 321.4757 322.0489 321.7571 322.4195 323.8651    15
#2 cores
OpenMPController::omp_set_num_threads(2)
## [[1]]
## [1] 2
microbenchmark(fun(),times=15)
## Unit: milliseconds
##   expr      min       lq     mean median      uq      max neval
##  fun() 161.9575 163.2676 165.7719 164.66 165.988 175.4034    15
#3 cores, etc.
OpenMPController::omp_set_num_threads(3)
## [[1]]
## [1] 3
microbenchmark(fun(),times=15)
## Unit: milliseconds
##   expr      min       lq     mean   median       uq      max neval
##  fun() 116.8546 129.1712 137.0371 132.9894 143.9082 171.7494    15

If CImg’s parallelisation doesn’t seem to work on your machine, it’s probably because you compiled the package with clang, which has patchy support for OpenMP. Recompile using gcc if possible.

3 Parallelisation in R vs. native parallelisation

Here’s a simple benchmark: medianblur can be parallelised across image channels. First, the R version using mclapply:

OpenMPController::omp_set_num_threads(1)
## [[1]]
## [1] 1
fun.R <- function() imsplit(boats,"c") %>% mclapply(function(v) medianblur(v,50),mc.cores=3)
microbenchmark(fun.R(),times=20)
## Unit: seconds
##     expr      min       lq    mean   median       uq      max neval
##  fun.R() 2.800479 2.807245 2.86281 2.811756 2.858016 3.381394    20

Second, CImg’s native version:

OpenMPController::omp_set_num_threads(3)
## [[1]]
## [1] 3
fun.nat <- function() medianblur(boats,50)
microbenchmark(fun.nat(),times=20)
## Unit: seconds
##       expr     min       lq     mean   median       uq      max neval
##  fun.nat() 2.76762 2.770584 2.771579 2.771564 2.772501 2.776503    20

Pros and cons of using native parallelisation:

Pros and cons of parallelisation from R:

Note that both types of parallelisation can be combined if you can spread the load over several machines.