diff --git a/R/iterators.R b/R/iterators.R index 95f20d938a9..70781b467ba 100644 --- a/R/iterators.R +++ b/R/iterators.R @@ -313,8 +313,21 @@ unsafe_create_vs <- function(graph, idx, verts = NULL) { if (is.null(verts)) { verts <- V(graph) } - res <- simple_vs_index(verts, idx, na_ok = TRUE) - add_vses_graph_ref(res, graph) + # `idx` are vertex IDs straight from C, and `verts` is the full `V(graph)`, + # so `verts[idx]` would just be `idx` again -- skip that copy and use the + # IDs directly as the payload. Names are subset from `verts`, and the graph + # reference is shared from `verts`. All attributes are set in one + # `attributes<-` call to avoid the per-object shallow copies that dominate + # when many sequences are built (e.g. `max_cliques()`). + vertex_names <- attr(verts, "names") + res <- as.integer(idx) + attributes(res) <- list( + names = if (is.null(vertex_names)) NULL else vertex_names[idx], + class = "igraph.vs", + env = attr(verts, "env"), + graph = attr(verts, "graph") + ) + res } # Internal function to quickly convert integer vectors to igraph.es @@ -325,8 +338,9 @@ unsafe_create_es <- function(graph, idx, es = NULL) { if (is.null(es)) { es <- E(graph) } - res <- simple_es_index(es, idx, na_ok = TRUE) - add_vses_graph_ref(res, graph) + # `simple_es_index()` already carries the graph reference over from `es`, + # so the weak reference built once by `E(graph)` is shared across calls. + simple_es_index(es, idx, na_ok = TRUE) } @@ -487,7 +501,19 @@ simple_vs_index <- function(x, i, na_ok = FALSE) { if (!na_ok && anyNA(res)) { cli::cli_abort("Unknown vertex selected.") } - class(res) <- "igraph.vs" + # Set every attribute in a single `attributes<-` call rather than one + # `attr<-`/`class<-` at a time: each incremental assignment shallow-copies + # the vector, and that copying dominates when many sequences are built + # (e.g. `max_cliques()`). `names` is carried over from the subset above; + # env/graph are carried from `x`, mirroring `simple_es_index()`, so + # sequences derived from one `V(graph)` share its weak reference instead of + # each minting a fresh one. + attributes(res) <- list( + names = attr(res, "names"), + class = "igraph.vs", + env = attr(x, "env"), + graph = attr(x, "graph") + ) res } diff --git a/touchstone/script.R b/touchstone/script.R index fce025cd817..cc4900e25ac 100644 --- a/touchstone/script.R +++ b/touchstone/script.R @@ -254,5 +254,82 @@ benchmark_run( n = 20 ) +# --------------------------------------------------------------------------- +# Group #5 - vertex/edge sequence construction on named graphs +# Functions that return (many) vertex/edge sequences pay for building the +# `names`/`vnames` attribute and attaching a graph reference to every object. +# These benchmarks exercise that construction path on *named* graphs, where +# the cost is highest. `max_cliques()` is the canonical case: it returns tens +# of thousands of vertex sequences, one per clique. +# --------------------------------------------------------------------------- +benchmark_run( + expr_before_benchmark = { + library(igraph) + set.seed(42) + g <- sample_gnp(200L, 0.16, directed = FALSE) + V(g)$name <- paste0("v", seq_len(gorder(g))) + for (i in 1:2) { + max_cliques(g) + } + gc(full = TRUE) + }, + max_cliques_named = for (i in 1:4) { + max_cliques(g) + }, + n = 20 +) + +benchmark_run( + expr_before_benchmark = { + library(igraph) + set.seed(42) + g <- sample_gnm(1000L, 5000L) + V(g)$name <- paste0("v", seq_len(1000L)) + es <- E(g) + for (i in 1:5) { + head_of(g, es) + } + gc(full = TRUE) + }, + head_of_named = for (i in 1:320) { + head_of(g, es) + }, + n = 20 +) + +benchmark_run( + expr_before_benchmark = { + library(igraph) + set.seed(42) + g <- sample_gnm(20000L, 50000L) + V(g)$name <- paste0("v", seq_len(20000L)) + for (i in 1:5) { + V(g) + } + gc(full = TRUE) + }, + V_named = for (i in 1:2700) { + V(g) + }, + n = 20 +) + +benchmark_run( + expr_before_benchmark = { + library(igraph) + set.seed(42) + g <- sample_gnm(20000L, 50000L) + V(g)$name <- paste0("v", seq_len(20000L)) + for (i in 1:2) { + E(g) + } + gc(full = TRUE) + }, + E_named = for (i in 1:15) { + E(g) + }, + n = 20 +) + # Create the artifacts consumed by the GitHub Action. benchmark_analyze()