Skip to content

Commit e42ef42

Browse files
yihuiclaude
andcommitted
perf: redesign cache pruning based on empirical measurement
Profiling revealed that object.size() overcounts gs_power_npe cache entries by ~600x (reports 1.8 MB per entry when true incremental cost is ~3 KB). This is because object.size() walks into shared namespace environments of function arguments, counting the same gsDesign2 namespace (833 KB) and gsDesign namespace (75 KB) for every entry. Changes: - Remove object.size() from the pruning path (both slow and inaccurate) - Only check entry count before insertions, not on cache hits - Set max_entries = 1024, justified by: - True cost: ~3 KB (gs_power_npe) to ~5 KB (ahr) per entry - 1024 entries ≈ 3-5 MB real memory - Supports ~200 cached designs in a session - A single design creates only 5-28 entries Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7e8d5d5 commit e42ef42

1 file changed

Lines changed: 9 additions & 6 deletions

File tree

R/utils.R

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,10 @@ cache_fun <- function(fun, ...) {
7474
h <- hashtab()
7575
sethash(fun_hash, fun, h)
7676
}
77-
prune_hash(h)
7877
args <- list(...)
7978
if (is.null(res <- gethash(h, args))) {
79+
# only prune before inserting (not on cache hits)
80+
prune_hash(h)
8081
res <- fun(...)
8182
sethash(h, args, res)
8283
}
@@ -87,11 +88,13 @@ cache_fun <- function(fun, ...) {
8788
res
8889
}
8990

90-
# prune a hash table to prevent it from growing too big. Uses numhash() for an
91-
# O(1) entry count check on each call. Clears when the count exceeds the limit.
92-
# With max_entries = 100 and typical entries of a few KB each, memory usage per
93-
# function is bounded to well under 1MB.
94-
prune_hash <- function(h, max_entries = 100L) {
91+
# Prune a hash table when it grows too large. We use a pure entry-count limit
92+
# because object.size() is both slow (~2ms) and wildly inaccurate for our use
93+
# case: it overcounts by ~600x for gs_power_npe entries because it walks into
94+
# shared namespace environments of function arguments. True incremental cost per
95+
# entry is ~3 KB (gs_power_npe) to ~5 KB (ahr), so 1024 entries ≈ 3-5 MB real
96+
# memory — well within acceptable limits for an interactive R session.
97+
prune_hash <- function(h, max_entries = 1024L) {
9598
if (numhash(h) > max_entries) clrhash(h)
9699
}
97100

0 commit comments

Comments
 (0)