--- title: "Performance" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Performance} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} library(S7) ``` The dispatch performance should be roughly on par with S3 and S4, though as this is implemented in a package there is some overhead due to `.Call` vs `.Primitive`. ```{r performance, cache = FALSE} Text <- new_class("Text", parent = class_character) Number <- new_class("Number", parent = class_double) x <- Text("hi") y <- Number(1) foo_S7 <- new_generic("foo_S7", "x") method(foo_S7, Text) <- function(x, ...) paste0(x, "-foo") foo_S3 <- function(x, ...) { UseMethod("foo_S3") } foo_S3.Text <- function(x, ...) { paste0(x, "-foo") } library(methods) setOldClass(c("Number", "numeric", "S7_object")) setOldClass(c("Text", "character", "S7_object")) setGeneric("foo_S4", function(x, ...) standardGeneric("foo_S4")) setMethod("foo_S4", c("Text"), function(x, ...) paste0(x, "-foo")) # Measure performance of single dispatch bench::mark(foo_S7(x), foo_S3(x), foo_S4(x)) bar_S7 <- new_generic("bar_S7", c("x", "y")) method(bar_S7, list(Text, Number)) <- function(x, y, ...) paste0(x, "-", y, "-bar") setGeneric("bar_S4", function(x, y, ...) standardGeneric("bar_S4")) setMethod("bar_S4", c("Text", "Number"), function(x, y, ...) paste0(x, "-", y, "-bar")) # Measure performance of double dispatch bench::mark(bar_S7(x, y), bar_S4(x, y)) ``` A potential optimization is caching based on the class names, but lookup should be fast without this. The following benchmark generates a class hierarchy of different levels and lengths of class names and compares the time to dispatch on the first class in the hierarchy vs the time to dispatch on the last class. We find that even in very extreme cases (e.g. 100 deep hierarchy 100 of character class names) the overhead is reasonable, and for more reasonable cases (e.g. 10 deep hierarchy of 15 character class names) the overhead is basically negligible. ```{r performance-2, message = FALSE, R.options = list(width = 120), cache = TRUE} library(S7) gen_character <- function (n, min = 5, max = 25, values = c(letters, LETTERS, 0:9)) { lengths <- sample(min:max, replace = TRUE, size = n) values <- sample(values, sum(lengths), replace = TRUE) starts <- c(1, cumsum(lengths)[-n] + 1) ends <- cumsum(lengths) mapply(function(start, end) paste0(values[start:end], collapse=""), starts, ends) } bench::press( num_classes = c(3, 5, 10, 50, 100), class_nchar = c(15, 100), { # Construct a class hierarchy with that number of classes Text <- new_class("Text", parent = class_character) parent <- Text classes <- gen_character(num_classes, min = class_nchar, max = class_nchar) env <- new.env() for (x in classes) { assign(x, new_class(x, parent = parent), env) parent <- get(x, env) } # Get the last defined class cls <- parent # Construct an object of that class x <- do.call(cls, list("hi")) # Define a generic and a method for the last class (best case scenario) foo_S7 <- new_generic("foo_S7", "x") method(foo_S7, cls) <- function(x, ...) paste0(x, "-foo") # Define a generic and a method for the first class (worst case scenario) foo2_S7 <- new_generic("foo2_S7", "x") method(foo2_S7, S7_object) <- function(x, ...) paste0(x, "-foo") bench::mark( best = foo_S7(x), worst = foo2_S7(x) ) } ) ``` And the same benchmark using double-dispatch ```{r performance-3, message = FALSE, R.options = list(width = 120), cache = TRUE} bench::press( num_classes = c(3, 5, 10, 50, 100), class_nchar = c(15, 100), { # Construct a class hierarchy with that number of classes Text <- new_class("Text", parent = class_character) parent <- Text classes <- gen_character(num_classes, min = class_nchar, max = class_nchar) env <- new.env() for (x in classes) { assign(x, new_class(x, parent = parent), env) parent <- get(x, env) } # Get the last defined class cls <- parent # Construct an object of that class x <- do.call(cls, list("hi")) y <- do.call(cls, list("ho")) # Define a generic and a method for the last class (best case scenario) foo_S7 <- new_generic("foo_S7", c("x", "y")) method(foo_S7, list(cls, cls)) <- function(x, y, ...) paste0(x, y, "-foo") # Define a generic and a method for the first class (worst case scenario) foo2_S7 <- new_generic("foo2_S7", c("x", "y")) method(foo2_S7, list(S7_object, S7_object)) <- function(x, y, ...) paste0(x, y, "-foo") bench::mark( best = foo_S7(x, y), worst = foo2_S7(x, y) ) } ) ```