| R | 4.6.1 |
| Julia | 1.13.2 |
| CPU | AMD Ryzen 7 8845HS w/ Radeon 780M Graphics |
| jlbind | 0.0.0.9000 |
| JuliaCall | 0.17.6 |
| JuliaConnectoR | 1.1.6 |
| JuliaCall commit | 947d1f3 |
Performance
Source: vignettes/articles/performance.qmd
jlbind calls Julia code that was compiled ahead of time, through one .Call() per function. The alternatives, JuliaCall and JuliaConnectoR, run Julia itself: JuliaCall embeds a Julia session in the R process, and JuliaConnectoR starts one in a separate process and talks to it over a socket. This article compares the three on the same Julia functions.
In short, on this machine, jlbind is ready to use 84 to 109 times sooner, and a call costs 0.702 µs rather than 8.76 µs (JuliaCall) or 207 µs (JuliaConnectoR).
The benchmarks
The functions are those of benchjl, a minimal jlbind client kept next to the benchmark script:
greet(name::String) = "Hello, " * name * "!"
shift(x::Vector{Float64}, by::Float64) = x .+ byjlbind calls them through benchjl, compiled into a jlbind library. JuliaCall and JuliaConnectoR evaluate the same definitions and call them with julia_call() and juliaFun(). Each interface runs in fresh R processes, since jlbind and JuliaCall can’t share one.
-
Startup: the time from loading the package to the result of the first call,
greet("world"), in a new R process (R’s own startup excluded), repeated 10 times. -
Call overhead: the median time of
greet("world"), a string in and a string out, after the first call. -
Data transfer: the median time of
shift(x, 1)for numeric vectorsxof length 10 to 107, which copiesxto Julia and the result back.
The benchmark script is vignettes/articles/performance/benchmark.R. The results below were run on1:
Startup
| Interface | Median | Min | Max |
|---|---|---|---|
| jlbind | 36.2 ms | 32.6 ms | 40.1 ms |
| JuliaCall | 3.94 s | 3.88 s | 4.28 s |
| JuliaConnectoR | 3.02 s | 2.99 s | 3.16 s |
For jlbind, startup is loading the compiled library and binding the package’s routines. JuliaCall and JuliaConnectoR start Julia, and compile each function the first time it is called. With a real Julia package instead of two one-line functions, compiling and loading the package typically take much longer than here.
Call overhead
| Interface | Time per call | Relative to jlbind |
|---|---|---|
| jlbind | 0.702 µs | 1× |
| JuliaCall | 8.76 µs | 12× |
| JuliaConnectoR | 207 µs | 295× |
A jlbind call is a .Call() into the compiled library, where the arguments are converted with R’s C API and the function is called directly. JuliaCall converts arguments and looks up the function at run time in the embedded session. JuliaConnectoR serializes the call to a separate Julia process and the result back, so each call costs a round trip.
Data transfer

| Length | jlbind | JuliaCall | JuliaConnectoR |
|---|---|---|---|
| 10 | 1.14 µs | 11.1 µs | 237 µs |
| 100 | 1.33 µs | 14.5 µs | 261 µs |
| 1,000 | 11.6 µs | 43.6 µs | 262 µs |
| 10,000 | 72.8 µs | 292 µs | 389 µs |
| 100,000 | 175 µs | 2.95 ms | 2.16 ms |
| 1,000,000 | 4.88 ms | 33.8 ms | 18.9 ms |
| 10,000,000 | 118 ms | 471 ms | 291 ms |
Footnotes
Using the development version of JuliaCall, which fixes several issues, including failing on Ubuntu 24 (JuliaCall#265).↩︎