The webR project is an innovative version of R that has been compiled for the browser and Node.js using WebAssembly, which is a low-level language used for the web. This means that users can run R code directly in their web browser without requiring an R server to execute the code.
The annoucement demonstrated the basic functionality of webR including the capability to emulate an R REPL in web browser, execute R code, load R packages, and plot data. Further, it also provides simple examples of how to embed webR in web pages. The documentation provides a detailed introduction to the project.
In this post, I would like to do some simple benchmark to get a rough impression of the performance of webR at the moment. These benchmarks are all done on the same machine, a MacBook Pro (2.3 GHz 8-Core Intel Core i9, 16 GB 2667 MHz DDR4, macOS 13.2.1), and webR is running at https://webr.r-wasm.org/latest/ in Firefox 111.0.
To make things simple, I just use system.time()
for each example.
Example 1: Basic arithmetic operations
The first example is to do some basic arithmetic operations. The code is as follows:
# Generate two random vectors of length 10^7
set.seed(123)
x <- rnorm(10^7)
y <- rnorm(10^7)
# Benchmark the time it takes to add the two vectors
system.time({
for (i in 1:100) {
z <- x + y
}
})
Native R
user system elapsed
2.117 0.608 2.728
webR
user system elapsed
0.000 0.000 2.493
Example 2: Matrix multiplication
The second example is to do matrix multiplication. The code is as follows:
# Generate two random matrices of size 1000 x 1000
set.seed(123)
x <- matrix(rnorm(1000^2), ncol = 1000)
y <- matrix(rnorm(1000^2), ncol = 1000)
# Benchmark the time it takes to multiply the two matrices
system.time(z <- x %*% y)
Native R
user system elapsed
0.557 0.001 0.559
webR
user system elapsed
0.00 0.00 1.96
Example 3: Sorting a large vector
The third example is to sort a large vector. The code is as follows:
# Generate a random vector of length 10^7
set.seed(123)
x <- rnorm(10^7)
# Benchmark the time it takes to sort the vector
system.time(sort(x))
Native R
user system elapsed
0.711 0.012 0.726
webR
user system elapsed
0.000 0.000 1.143
Example 4: Running a simulation
The fourth example is to run a simulation of coin flips. The code is as follows:
# Define a function to simulate a coin flip
simulate_coin_flip <- function(n_flips) {
outcomes <- sample(c("heads", "tails"), n_flips, replace = TRUE)
sum(outcomes == "heads")
}
# Run a simulation with 100 million coin flips
system.time({
result <- simulate_coin_flip(1e8)
})
Native R
user system elapsed
4.849 0.728 5.854
webR
user system elapsed
0.00 0.00 7.31
Example 5: Generating random numbers from a distribution
# Generate 10 million random numbers from a normal distribution
system.time({
result <- rnorm(1e7)
})
Native R
user system elapsed
0.563 0.021 0.587
webR
user system elapsed
0.000 0.000 0.613
Example 6: Merging two large data frames
# Generate two data frames with 1 million rows each
set.seed(123)
df1 <- data.frame(id = 1:1e6, value1 = rnorm(1e6))
df2 <- data.frame(id = 1:1e6, value2 = rnorm(1e6))
# Benchmark the time it takes to merge the data frames
system.time(merge(df1, df2))
Native R
user system elapsed
0.729 0.036 0.768
webR
user system elapsed
0.000 0.000 1.326
Example 7: Finding the maximum value in a large vector
# Generate a random vector of length 10^7
set.seed(123)
x <- rnorm(10^7)
# Benchmark the time it takes to find the maximum value
system.time(max(x))
Native R
user system elapsed
0.222 0.001 0.224
webR
user system elapsed
0.00 0.00 0.23
Example 8: Calculating quantiles of a large vector
# Generate a random vector of length 10^7
set.seed(123)
x <- rnorm(10^7)
# Benchmark the time it takes to find the maximum value
system.time(quantile(x, seq(0, 1, 0.05)))
Native R
user system elapsed
1.039 0.035 1.076
webR
user system elapsed
0.000 0.000 1.143
Example 9: Fitting a linear regression model
# Generate two random vectors of length 10^6
set.seed(123)
x <- rnorm(10^6)
y <- rnorm(10^6)
z <- rnorm(10^6)
# Benchmark the time it takes to fit a linear regression model
system.time(lm(z ~ x + y))
Native R
user system elapsed
0.226 0.055 0.282
webR
user system elapsed
0.000 0.000 0.268
Example 10: Generating a large list of random numbers
# Generate a list of 1 million items of 10 random numbers
system.time({
mylist <- lapply(1:1e6, function(x) rnorm(10))
})
Native R
user system elapsed
3.499 0.332 3.854
webR
user system elapsed
0.000 0.000 9.485
Example 11: Sorting a large vector
# Generate a random vector of length 10^7
set.seed(123)
x <- rnorm(10^7)
# Benchmark the time it takes to sort the vector
system.time(sort(x))
Native R
user system elapsed
0.770 0.084 0.857
webR
user system elapsed
0.000 0.000 1.158
Example 12: Inverting a large matrix
# Generate a random matrix of size 1000 x 1000
set.seed(123)
x <- matrix(rnorm(1000^2), ncol = 1000)
# Benchmark the time it takes to invert the matrix
system.time(solve(x))
Native R
user system elapsed
1.610 0.010 2.046
webR
user system elapsed
0.000 0.000 2.321
Conclusion
From the very rough benchmark above, it looks like webR and native R are mostly comparable in terms of performance in most examples above. However, there are some examples where webR is noteably slower than native R. For example, in the example of running a simulation of coin flips, webR is about 1.5 times slower than native R. In the example of generating a large list of random numbers, webR is about 3 times slower than native R. These two examples both involve intensive allocation of R objects, which might be not yet optimized in webR. But, it is already extremely promising given that webR is still in its early development stage.
The webR-quarto-demos and the JupterLite instance with the webR kernel are amazing demos. I’m considering if we could embed webR in vscode-R extension so that we don’t need a native R installation to provide some basic code editing features.
I’m looking forward to seeing more demos and applications of webR.