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 
##  12.413   0.536  12.945
#Parallel version: use mclapply
fun.par <- function() imsplit(im,"c") %>% mclapply(rank,mc.cores=2)
system.time(fun.par())
##    user  system elapsed 
##   4.790   0.608  10.250

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() 463.7745 465.425 469.6804 466.8861 468.3055 499.1592    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() 250.1561 260.1025 262.2085 264.3445 266.5629 268.8653    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() 295.3284 297.5541 300.0099 299.4172 302.8274 306.2033    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() 4.840825 5.21166 5.275059 5.291631 5.363318 5.467941    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() 5.140669 5.220139 5.39972 5.270974 5.475182 6.057458    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.