При добавлении списка в R в результате копирования?
Предположим, что я создал список из R и добавлю его следующим образом:
x = list(10)
x[[2]] = 20
Это эквивалентно
x = list(10)
x = list(10, 20)
? Я не очень разбираюсь в конкретных деталях того, как R обрабатывает списки в памяти, но мое ограниченное понимание состоит в том, что он имеет тенденцию быть счастливым от копирования; то, что было бы идеальным для меня, было бы то, что первый вариант не предполагает по существу создания другого списка в памяти, а просто приводит к тому, чтобы отложить новое место в памяти для добавленного значения. По сути, если у меня есть большой список, я не хочу, чтобы R сделал еще одну копию, если я просто хочу что-то добавить к нему.
Если поведение, которое я хочу, не то, что здесь дано, есть ли другой способ получить желаемый эффект?
Ответы
Ответ 1
Я уверен, что ответ "нет". Я использовал следующий код для двойной проверки:
Rprof(tmp <- tempfile(), memory.profiling = TRUE)
x <- list()
for (i in 1:100) x[[i]] <- runif(10000)
Rprof()
summaryRprof(tmp, memory = "stats")
unlink(tmp)
Выход:
# index: runif
# vsize.small max.vsize.small vsize.large max.vsize.large
# 76411 381781 424523 1504387
# nodes max.nodes duplications tot.duplications
# 2725878 13583136 0 0
# samples
# 5
Соответствующая часть duplications = 0
.
Ответ 2
Matthew Dowle answer здесь, и обоснование эффективности работы с памятью состоит в том, чтобы остановить множество копий за кулисами <-
, [<-
, [[<-
и другие базовые операции R
(names
и т.д.)
[[<-
скопирует все x
. См. Пример ниже
x <- list(20)
tracemem(x)
#[1] "<0x2b0e2790>"
x[[2]] <- 20
# tracemem[0x2b0e2790 -> 0x2adb7798]:
Ваш второй случай
x <- list(10,20)
на самом деле не добавляет оригинальный x
, а заменяет x
на объект, который является оригинальным x
с добавленным значением.
Ответ 3
Чтобы помочь мне разобраться, внесите ли изменение список из глубокой копии или мелкой копии, я создал небольшой эксперимент. Если изменение списка делает глубокую копию, то при изменении списка, содержащего большой объект, по сравнению со списком, содержащим небольшой объект, он должен быть медленнее:
z1 <- list(runif(1e7))
z2 <- list(1:10)
system.time({
for(i in 1:1e4) z1[1 + i] <- 1L
})
# user system elapsed
# 0.283 0.034 0.317
system.time({
for(i in 1:1e4) z2[1 + i] <- 1L
})
# user system elapsed
# 0.284 0.034 0.319
Тайминги на моем компьютере были в основном идентичными, предполагая, что копирование списка делает мелкую копию, копируя указатели на существующие структуры данных.
Ответ 4
Принял ответ на фланель, но подсказка Chase была хорошей, поэтому я подтвердил, что у меня есть желаемое поведение, используя его предложение использовать tracemem()
. Вот первый пример, который мы просто добавляем в список:
x = list(10)
tracemem(x[[1]])
# [1] "<0x2d03fa8>" #(likely different on each machine)
x[[2]] = 20
tracemem(x[[1]])
# [1] "<0x2d03fa8>"
И вот результат из второго примера, где мы создаем два списка:
x = list(10)
tracemem(x[[1]])
# [1] "<0x2d03c78>"
x = list(10, 20)
tracemem(x[[1]])
# [1] "<0x2d07ff8>"
Таким образом, первый метод дает желаемое поведение.