У меня есть унаследованный код R, который использует идиому, чтобы избежать копирования больших кадров данных. Когда функция, создающая фрейм данных, завершает работу, она не возвращает фрейм данных, а вместо этого сохраняет его в глобальном контексте среды:
assign(df_name, df, envir = globalenv())
После завершения функции вызывающий код извлекает фрейм данных следующим образом:
df <- get(df_name, envir = globalenv())
Мой вопрос заключается в следующем: действительно ли сама функция get()
создает копию, когда возвращает значение, тем самым создавая ту самую копию, которой эта идиома должна была избежать? Если да, то есть ли лучший способ сделать это?
Одним словом, это ерунда. Здесь копируются назначения два (как через assign
, так и через <-
). Просто возврат объекта из функции сохранил бы одну из копий.
Между прочим, само копирование — это дешевый, поскольку R реализует концепцию, называемую семантикой «копирование при записи»: приведенные выше команды логически делают копии, но физически это только увеличивает счетчик ссылок внутри скопированной ссылки. Фактические данные по ссылке копируются только тогда, когда вы изменяете данные через одну из ссылок.
Насколько я знаю, возврат чего-либо на самом деле не копирует это из одной области памяти в другую. Пара тестов:
trace_return <- function() {
df <- data.frame(a = 1:10, b = letters[1:10])
print(tracemem(df))
df
}
ans <- trace_return()
# "<00000188C114D578>"
ans$a[1L] <- 0L # copy triggered on modify
# tracemem[0x00000188c114d578 -> 0x00000188c10ab098]:
# tracemem[0x00000188c10ab098 -> 0x00000188c10ab258]: $<-.data.frame $<-
# tracemem[0x00000188c10ab258 -> 0x00000188c10ab358]: $<-.data.frame $<-
А также:
e <- new.env()
e$ans <- trace_return()
# "<00000188C13F8EF8>"
ans <- e$ans # no copy here
ans$b <- NULL
# tracemem[0x00000188c13f8ef8 -> 0x00000188c14293f8]:
# tracemem[0x00000188c14293f8 -> 0x00000188c1429378]: $<-.data.frame $<-
# tracemem[0x00000188c1429378 -> 0x00000188c1429278]: $<-.data.frame $<-
На самом деле не имеет значения, есть ли две копии одного и того же data.frame. Но если честно я не вижу смысла присваивать data.frame дважды.
Вы можете использовать пакет pryr
, чтобы увидеть, сколько памяти выделяет объект.
library(pryr)
df <- do.call(data.frame, replicate(8000, rep(FALSE, 8000), simplify=FALSE))
assign("dname_out", df, envir = globalenv())
mem_used()
337 MB
mem_change(df <- get("dname_out", envir = globalenv()))
736 B
> mem_used()
337 MB
Изменение в памяти составляет всего 736 байт, поэтому на самом деле вы не можете разбить свой компьютер, создавая тонны копий.