Replies: 5 comments 12 replies
-
I tried a similar pipeline without library(autometric)
library(targets)
if (tar_active()) {
log_start(
path = "logs/main.txt",
seconds = 1
)
}
tar_option_set(
memory = "transient",
garbage_collection = TRUE
)
write_file <- function(x) {
fs::dir_create("files")
path <- file.path("files", paste0(x, ".rds"))
saveRDS(x, path)
path
}
list(
tar_target(x, seq_len(2e4)),
tar_target(y, write_file(x), pattern = map(x), format = "file"),
tar_target(z, readRDS(y), pattern = map(y))
) The pipeline took a lot longer to run (~7 hr), but memory usage looked more reasonable: There is a mild surge at the beginning, a mild surge at around 10000s (presumably when all the dynamic branches of z are defined) and then another mild surge at the end. A max of 800 MB is pretty good. Takeaways:
So we actually have 2 different unrelated performance problems. |
Beta Was this translation helpful? Give feedback.
-
For (2), the slowness just comes from garbage collection 😆 . I should have known. library(targets)
tar_option_set(
memory = "transient",
garbage_collection = TRUE
)
write_file <- function(x) {
fs::dir_create("files")
path <- file.path("files", paste0(x, ".rds"))
saveRDS(x, path)
path
}
list(
tar_target(x, seq_len(1000)),
tar_target(y, write_file(x), pattern = map(x), format = "file"),
tar_target(z, readRDS(y), pattern = map(y))
) library(proffer)
library(targets)
tar_destroy()
pprof(tar_make(callr_function = NULL, reporter = "summary")) |
Beta Was this translation helpful? Give feedback.
-
As best I can tell for now, most of the memory is consumed by the internal data structures |
Beta Was this translation helpful? Give feedback.
-
To the broader discussion: here is a rough sketch of how current <- list2env(
list(
command = list2env(
list(
packages = c("dplyr", "tibble", "ggplot2"),
string = "expression(1)",
seed = -1813454154L,
hash = "bb375a30fa348382"
)
),
settings = list2env(
list(
name = "x", description = character(0), format = "rds",
repository = "local", pattern = NULL, dimensions = character(0),
iteration = "vector", error = "stop", memory = "persistent",
garbage_collection = FALSE, deployment = "worker", priority = 0,
storage = "main", retrieval = "main"
)
),
cue = list2env(
list(
mode = "thorough", command = TRUE, depend = TRUE, format = TRUE,
repository = TRUE, iteration = TRUE, file = TRUE, seed = TRUE
)
)
)
) The size of this object is 5432 bytes. library(lobstr)
as.numeric(obj_size(current))
#> [1] 5432 Here is a flattened list representation of all the elements, with comments to show how many bytes it would take to store each object in C. flat <- list(
# 18 regular characters +
# 3 '\0' characters +
# 3 * sizeof(char*) +
# sizeof(char**) =
# 18 + 3 + 3 * 8 + 8 =
# 53 bytes
packages = c("dplyr", "tibble", "ggplot2"),
# 13 regular characters +
# 1 '\0' character +
# sizeof(char*) =
# 13 + 1 + 8 =
# 22 bytes
string = "expression(1)",
# 4 bytes
seed = -1813454154L,
# 16 + 1 + 8 =
# 25 bytes
hash = "bb375a30fa348382",
# 1 + 1 + 8 =
# 10 bytes
name = "x",
# 8 bytes
description = character(0),
# 12 bytes
format = "rds",
# 14 bytes
repository = "local",
# 8 bytes
pattern = NULL,
# 8 bytes
dimensions = character(0),
# 15 bytes
iteration = "vector",
# 13 bytes
error = "stop",
# 19 bytes
memory = "persistent",
# 1 byte
garbage_collection = FALSE,
# 15 bytes
deployment = "worker",
# 8 bytes
priority = 0,
# 13 bytes
storage = "main",
# 13 bytes
retrieval = "main",
# 17 bytes
mode = "thorough",
# 1 byte
command = TRUE,
# 1 byte
depend = TRUE,
# 1 byte
format = TRUE,
# 1 byte
repository = TRUE,
# 1 byte
iteration = TRUE,
# 1 byte
file = TRUE,
# 1 byte
seed = TRUE
) The R representations of the individual elements of sum(vapply(flat, obj_size, FUN.VALUE = numeric(1L)))
#> [1] 2296 Summing all the alleged C storage sizes in the comments, we get 285 bytes. If represented in a C struct, the total size would actually be larger because of alignment and padding: for now, I'll guess around 100 extra bytes for padding, totaling 385 bytes (about 7% of ‘current’). Interestingly, microbenchmark::microbenchmark(qs::qserialize(current))
#> Unit: microseconds
#> expr min lq mean median uq max neval
#> qs::qserialize(current) 54.407 57.728 69.40685 63.509 70.151 243.745 100 Using the full pipeline below: library(targets)
library(tibble)
list(
tar_target(data, tibble(x = seq_len(1e4))),
tar_target(slice, data, pattern = map(data))
) and taking the target definition object for the pattern target as.numeric(obj_size(target))
#> [1] 1771336
as.numeric(obj_size(qs::qserialize(target)))
#> 199064 Serialization times are in the low milliseconds: > microbenchmark::microbenchmark(qs::qserialize(target))
Unit: milliseconds
expr min lq mean
qs::qserialize(target) 2.872583 2.900934 2.9433
median uq max neval
2.91633 2.961143 3.188201 100 Of course most targets will be branches, not patterns. A single branch is about 10.56 KB and serializes to 512 B (5% of the original size) and serialization times are about 50-55 microseconds. Unfortunately |
Beta Was this translation helpful? Give feedback.
-
On a closer look, |
Beta Was this translation helpful? Give feedback.
-
c.f. #1347 and #1329. I tried the following pipeline on a RHEL9 node:
Then I read and visualized the
autometric
logs:The
crew
worker andmirai
dispatcher are efficient with memory, consuming no more than a few megabytes. But the memory consumption of the localtargets
process kept increasing without an ostensible bound. 3 GB isn't necessarily alarming, but I will need to look into what is responsible for most of this memory.I wonder if this could explain #1347 or #1329, and I wonder what would happen without
crew
.Beta Was this translation helpful? Give feedback.
All reactions