Home > Software engineering >  Poor Julia performance from R
Poor Julia performance from R

Time:12-14

I've been studying the idea of using Julia as a replacement for C as a secondary language to speed up R scripts. Therefore, I'm writing a short script to compare the performance of the three languages, using an R script as the main file reproduced below:

# Loading packages
library(Rcpp)
library(RcppArmadillo)
library(JuliaConnectoR)
library(microbenchmark)

# Generating data
set.seed(9392927)
N <- 1e3L
x1 <- matrix(rpois(N ^ 2L, lambda = 10L), N)
x2 <- matrix(rpois(N ^ 2L, lambda = 10L), N)

# Defining functions
mySolveR <- function(m) {
  return(solve(m))
}
myProdR <- function(m1, m2) {
  return(m1 %*% m2)
}
cppFunction("
  arma::mat mySolveCpp(arma::mat m) {
    return(inv(m));
  }",
  depends = "RcppArmadillo"
)
cppFunction("
  arma::mat myProdCpp(arma::mat m1, arma::mat m2) {
    return(m1 * m2);
  }",
  depends = "RcppArmadillo"
)
mySolveJulia <- juliaEval("
  function mySolveJulia(m)
    return(inv(m))
  end"
)
#> Starting Julia ...
myProdJulia <- juliaEval("
  function myProdJulia(m1, m2)
    return(m1 * m2)
  end"
)

# Benchmarking
reps <- 10L
print(
  microbenchmark(
      mySolveR(x1), mySolveCpp(x1), mySolveJulia(x1),
      times = reps
    )
)
#> Unit: milliseconds
#>              expr        min         lq      mean     median        uq
#>      mySolveR(x1)   41.53371   44.03872  186.4527   59.43063  465.0983
#>    mySolveCpp(x1)   49.42867   72.63049  240.0839   81.16933  440.1176
#>  mySolveJulia(x1) 3856.43147 3897.36013 5112.4448 5376.94133 6026.1369
#>        max neval cld
#>   528.8838    10  a 
#>   576.2336    10  a 
#>  6220.1093    10   b
print(
  microbenchmark(
    myProdR(x1, x2), myProdCpp(x1, x2), myProdJulia(x1, x2),
    times = reps
  )
)
#> Unit: milliseconds
#>                 expr        min         lq       mean     median         uq
#>      myProdR(x1, x2)   20.10827   23.50031   27.69398   25.18961   32.51614
#>    myProdCpp(x1, x2)   25.59671   29.59619   38.32624   31.83016   49.33401
#>  myProdJulia(x1, x2) 5698.10212 6135.62034 6353.71632 6412.39110 6487.12516
#>         max neval cld
#>    39.89406    10  a 
#>    57.82850    10  a 
#>  7243.81362    10   b

Created on 2022-12-14 with reprex v2.0.2

At this stage, I am not worried about which language performs best, I even suppose such simple functions would perform at the same order of magnitude, but I am surprised that Julia is performing so poorly.

My only guesses have to do with unnecessary recompilation of code or of Julia instance launches, but using startJuliaServer()/stopJulia() doesn't help, and neither does isolating the functions in their own source files. What am I missing?

CodePudding user response:

I have not used JuliaConnectoR, but I tested JuliaCall:

Setup (maybe things can be written in a cleaner way - I just checked JuliaCall manual; you can check sources of example packages listed here to see more carefully designed code):

library(JuliaCall)
library(microbenchmark)
julia <- julia_setup()
set.seed(9392927)
N <- 1e3L
x1 <- matrix(rpois(N ^ 2L, lambda = 10L), N)
x2 <- matrix(rpois(N ^ 2L, lambda = 10L), N)
mySolveR <- function(m) {
  return(solve(m))
}
myProdR <- function(m1, m2) {
  return(m1 %*% m2)
}
julia_eval("
function mySolveJulia(m)
      return(inv(m))
end")
mySolveJulia <- function(m) {
    julia_call("mySolveJulia", m)
}

julia_eval("
function myProdJulia(m1, m2)
    return(m1 * m2)
end")
myProdJulia <- function(m1, m2) {
    julia_call("myProdJulia", m1, m2)
}

And benchmark:

> reps <- 10L
> print(
    microbenchmark(
        mySolveR(x1), mySolveJulia(x1),
        times = reps
      )
  )
Unit: milliseconds
             expr      min       lq      mean    median       uq      max neval
     mySolveR(x1) 516.1157 517.4391 520.98363 520.60125 522.0257 531.6278    10
 mySolveJulia(x1)  50.8057  52.9256  73.29882  55.88265  67.5247 167.2614    10
> print(
    microbenchmark(
      myProdR(x1, x2), myProdJulia(x1, x2),
      times = reps
    )
  )
Unit: milliseconds
                expr      min       lq    mean   median       uq      max neval
     myProdR(x1, x2) 499.3371 509.4347 526.256 523.8693 532.5146 562.5851    10
 myProdJulia(x1, x2) 331.7332 332.5554 354.807 337.0199 351.8239 492.5586    10

Surprisingly, I have significantly higher timings for R than you (but maybe this is a difference in machines on which the test is run).

Also if you want to see how much time is spent in data transport here are benchmarks in Julia:

julia> using Distributions

julia> using BenchmarkTools

julia> x1 = rand(Poisson(10), 1000, 1000);

julia> x2 = rand(Poisson(10), 1000, 1000);

julia> @benchmark inv($x1)
BenchmarkTools.Trial: 168 samples with 1 evaluation.
 Range (min … max):  25.273 ms … 55.200 ms  ┊ GC (min … max): 0.00% … 9.74%
 Time  (median):     28.539 ms              ┊ GC (median):    0.00%
 Time  (mean ± σ):   29.815 ms ±  4.172 ms  ┊ GC (mean ± σ):  2.40% ± 3.90%

     ▃█▂▃▁▂ ▄▆▁▄  ▂
  ▃▁███████▇████▇▄█▅▅█▃▃▁▁▅▄▄▁▁▁▄▃▄▅▃▁▃▁▁▃▃▇▁▁▁▃▁▃▁▄▁▁▁▁▃▁▃▁▃ ▃
  25.3 ms         Histogram: frequency by time        42.1 ms <

 Memory estimate: 15.76 MiB, allocs estimate: 7.

julia> @benchmark $x1 * $x2
BenchmarkTools.Trial: 17 samples with 1 evaluation.
 Range (min … max):  293.104 ms … 321.683 ms  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     309.639 ms               ┊ GC (median):    0.00%
 Time  (mean ± σ):   309.618 ms ±   8.177 ms  ┊ GC (mean ± σ):  0.11% ± 0.31%

  ▁            ▁   ▁  ▁▁     ▁    ▁ ▁█ ▁    ▁        ▁ ▁   ▁▁ ▁
  █▁▁▁▁▁▁▁▁▁▁▁▁█▁▁▁█▁▁██▁▁▁▁▁█▁▁▁▁█▁██▁█▁▁▁▁█▁▁▁▁▁▁▁▁█▁█▁▁▁██▁█ ▁
  293 ms           Histogram: frequency by time          322 ms <

 Memory estimate: 7.66 MiB, allocs estimate: 5.

CodePudding user response:

With your code you restart Julia at each call. Below is how I would do. But I don't get a better timing with Julia, contrary to @BogumilKaminksi. I don't know why.

library(JuliaConnectoR)

juliaModule <- "
module myJuliaModule
export mySolveJulia
export myProdJulia

function mySolveJulia(m)
  return inv(m)
end

function myProdJulia(m1, m2)
  return m1 * m2
end

end
"

. <- juliaEval(juliaModule)
myJuliaModule <- juliaImport(".myJuliaModule", all = FALSE)
mySolveJulia <- myJuliaModule$mySolveJulia

mySolveR <- function(x) solve(x)
set.seed(9392927)
N <- 1e3L
x1 <- matrix(rpois(N ^ 2L, lambda = 10L), N)
x2 <- matrix(rpois(N ^ 2L, lambda = 10L), N)

reps <- 10L
microbenchmark::microbenchmark(
  mySolveR(x1), 
  mySolveJulia(x1),
  times = reps
)

stopJulia()
  • Related