Skip to contents

Computational Efficiency: ttm vs rTensor

We compare the computational efficiency of the ttm function from the tensory package with the equivalent function in the rTensor package. This benchmark demonstrates the performance benefits of using tensory for tensor-times-matrix multiplication.

set.seed(123)

# Create a random 3-mode tensor and a matrix
X <- array(runif(30*40*50), dim = c(30, 40, 50))
mat <- matrix(runif(20*40), nrow = 20, ncol = 40)

library(bench)

# Prepare rTensor object
X_rTensor <- rTensor::as.tensor(X)
X <- tensory::Tensor$new(X)

# Benchmark ttm in tensory vs rTensor
bm <- bench::mark(
  tensory = tensory::ttm(X, mat, mode = 2)$data,
  rTensor = rTensor::ttm(X_rTensor, mat, m = 2)@data,
  min_iterations = 50,
  check = FALSE
)
bm
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 tensory      1.74ms   1.84ms      514.   511.1KB     4.11
#> 2 rTensor      1.76ms   1.82ms      538.     1.2MB    15.4



# Visualization
library(ggplot2)
library(dplyr)
library(tidyr)
autoplot(bm, type = "violin")


bm %>%
  unnest(c(time, gc)) %>%
  filter(gc == "none") %>%
  mutate(expression = as.character(expression)) %>%
  ggplot(aes(x = mem_alloc, y = time, color = expression)) +
  geom_point() +
  scale_color_bench_expr(scales::brewer_pal(type = "qual", palette = 3))

set.seed(123)

# Create a random 3-mode tensor and a matrix
X <- array(runif(10*100*50), dim = c(10, 100, 50))
mat <- matrix(runif(100*100), nrow = 100, ncol = 100)

library(bench)

# Prepare rTensor object
X_rTensor <- rTensor::as.tensor(X)
X <- tensory::Tensor$new(X)

# Benchmark ttm in tensory vs rTensor
bm <- bench::mark(
  tensory = tensory::ttm(X, mat, mode = 2)$data,
  rTensor = rTensor::ttm(X_rTensor, mat, m = 2)@data,
  iterations = 50,
  check = FALSE
)
bm
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 tensory      4.32ms   4.88ms      204.  781.67KB     4.16
#> 2 rTensor      4.32ms   5.19ms      197.    1.53MB     8.22

# Visualization
library(ggplot2)
autoplot(bm, type = "violin")

Multiple Matrix Multiplications Benchmark

Here we benchmark the performance of multiple matrix multiplications, which is a common operation in tensor decomposition algorithms and multilinear algebra.

set.seed(456)

# Create a 4-mode tensor and matrices for each mode
X3 <- array(runif(50*40*30*20), dim = c(50, 40, 30, 20))
matrices3 <- list(
  matrix(runif(6*50), nrow = 6, ncol = 50),  # mode 1
  matrix(runif(4*40), nrow = 4, ncol = 40),  # mode 2
  matrix(runif(3*30), nrow = 3, ncol = 30),  # mode 3
  matrix(runif(2*20), nrow = 2, ncol = 20)   # mode 4
)

library(bench)

# Prepare rTensor object
X3_rTensor <- rTensor::as.tensor(X3)
X3_tensor <- tensory::Tensor$new(X3)

# Define function for multiple rTensor ttm operations
multiple_ttm_rTensor <- function(tensor, mat_list) {
  result <- tensor
  for (i in seq_along(mat_list)) {
    result <- rTensor::ttm(result, mat_list[[i]], m = i)
  }
  return(result)
}

# Benchmark multiple ttm operations
bm_multiple <- bench::mark(
  tensory = tensory::ttm(X3_tensor, matrices3, mode = c(1, 2, 3, 4))$data,
  rTensor = multiple_ttm_rTensor(X3_rTensor, matrices3)@data,
  iterations = 50,
  check = FALSE
)
bm_multiple
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 tensory      12.3ms   14.3ms      70.5    1.08MB     4.50
#> 2 rTensor        13ms   13.3ms      73.7   14.07MB    73.7

# Visualization
library(ggplot2)
autoplot(bm_multiple, type = "violin")

set.seed(456)

# Create a 4-mode tensor and matrices for each mode
X <- array(runif(4^6), dim = rep(4,6))
a <- matrix(runif(4*4), nrow = 4, ncol = 4)
matrices <- lapply(1:6,function(xx) a)

library(bench)

# Prepare rTensor object
X_rTensor <- rTensor::as.tensor(X)
X <- tensory::Tensor$new(X)

# Define function for multiple rTensor ttm operations
multiple_ttm_rTensor <- function(tensor, mat_list) {
  result <- tensor
  for (i in seq_along(mat_list)) {
    result <- rTensor::ttm(result, mat_list[[i]], m = i)
  }
  return(result)
}

# Benchmark multiple ttm operations
bm_multiple <- bench::mark(
  tensory = tensory::ttm(X, matrices, mode = 1:6)$data,
  rTensor = multiple_ttm_rTensor(X_rTensor, matrices)@data,
  iterations = 10,
  check = FALSE
)
bm_multiple
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 tensory    954.77µs 986.36µs     1016.     225KB        0
#> 2 rTensor      2.01ms   2.05ms      486.     773KB        0

# Visualization
library(ggplot2)
autoplot(bm_multiple, type = "violin")

Memory Efficiency Comparison

Let’s also compare memory usage during these operations:

set.seed(789)

# Create a tensor and matrix for memory comparison
X <- array(runif(30*25*20), dim = c(30, 25, 20))
mat <- matrix(runif(35*25), nrow = 35, ncol = 25)

library(bench)

# Prepare rTensor object
X_rTensor <- rTensor::as.tensor(X)
X <- tensory::Tensor$new(X)

# Memory benchmark
bm_memory <- bench::mark(
  tensory = tensory::ttm(X, mat, mode = 2)$data,
  rTensor = rTensor::ttm(X_rTensor, mat, m = 2)@data,
  iterations = 20,
  check = FALSE
)
bm_memory
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 tensory       700µs    744µs     1357.     329KB        0
#> 2 rTensor       798µs    831µs     1201.     610KB        0

# Visualization
library(ggplot2)
autoplot(bm_memory, type = "violin")

Scalability Analysis

Finally, let’s analyze how performance scales with tensor size:

set.seed(101112)

# Test different tensor sizes
sizes <- c(20, 60, 100, 140, 160)
scalability_results <- data.frame()

library(bench)

for (size in sizes) {
  # Create tensor of varying size
  X <- array(runif(size^3), dim = c(size, size, size))
  mat <- matrix(runif((size) * size), nrow = size, ncol = size)

  # Prepare rTensor object
  X_rTensor <- rTensor::as.tensor(X)
  X <- tensory::Tensor$new(X)

  # Benchmark
  bm_scale <- bench::mark(
    tensory = tensory::ttm(X, mat, mode = 2)$data,
    rTensor = rTensor::ttm(X_rTensor, mat, m = 2)@data,
    iterations = 20,
    check = FALSE
  )

  # Store results
  temp_results <- data.frame(
    size = size,
    package = as.character(bm_scale$expression),
    median_time = as.numeric(bm_scale$median),
    memory = as.numeric(bm_scale$mem_alloc)
  )

  scalability_results <- rbind(scalability_results, temp_results)
}

# Visualization
library(ggplot2)
ggplot(scalability_results, aes(x = size, y = median_time, color = package)) +
  geom_line(size = 1.2) +
  geom_point(size = 3) +
  #scale_y_log10() +
  labs(title = "Scalability Comparison",
       subtitle = "Performance vs Tensor Size",
       x = "Tensor Dimension Size",
       y = "Median Time",
       color = "Package") +
  theme_minimal()

set.seed(101112)

# Test different tensor sizes
sizes <- c(20, 60, 100, 140, 160)
scalability_results <- data.frame()

library(bench)

for (size in sizes) {
  # Create tensor of varying size
  X <- array(runif(size*size*size), dim = c(size, size, size))
  mat <- matrix(runif((size+10) * size), nrow = size+10, ncol = size)

  # Prepare rTensor object
  X_rTensor <- rTensor::as.tensor(X)
  X <- tensory::Tensor$new(X)

  # Benchmark
  bm_scale <- bench::mark(
    tensory = tensory::ttm(X, mat, mode = 3)$data,
    rTensor = rTensor::ttm(X_rTensor, mat, m = 3)@data,
    iterations = 10,
    check = FALSE
  )

  # Store results
  temp_results <- data.frame(
    size = size,
    package = as.character(bm_scale$expression),
    median_time = as.numeric(bm_scale$median),
    mean_time = as.numeric(bm_scale$total_time/bm_scale$n_itr),
    memory = as.numeric(bm_scale$mem_alloc)
  )

  scalability_results <- rbind(scalability_results, temp_results)
}

# Visualization
library(ggplot2)
ggplot(scalability_results, aes(x = size, y = mean_time, color = package)) +
  geom_line(size = 1.2) +
  geom_point(size = 3) +
  #scale_y_log10() +
  labs(title = "Scalability Comparison",
       subtitle = "Performance vs Tensor Size",
       x = "Tensor Dimension Size",
       y = "Median Time",
       color = "Package") +
  theme_minimal()