【问题标题】:Pass an object to a function without copying it on change将对象传递给函数而不在更改时复制它
【发布时间】:2013-02-01 13:43:37
【问题描述】:

我的问题

如果将对象x 传递给修改它的函数f,R 将在f 的环境中创建x 的修改本地副本,而不是更改原始对象(由于副本变化原则)。但是,我有一种情况,x 非常大,一旦传递给f 就不需要它,所以我想避免在调用f 时存储x 的原始副本。有没有聪明的方法来实现这一点?

f 是一个未知函数,可能由不太聪明的用户提供。

我目前的解决方案

到目前为止,我最好的方法是将 x 包装在一个函数 forget 中,该函数对 x 进行新的本地引用,称为 y,删除工作区中的原始引用,然后传递新的参考。问题是我不确定它是否能完成我想要的,它只适用于globalenv(),这在我目前的情况下是一个交易破坏者。

forget <- function(x){
    y <- x
    # x and y now refers to the same object, which has not yet been copied
    print(tracemem(y))
    rm(list=deparse(substitute(x)), envir=globalenv())
    # The outside reference is now removed so modifying `y`
    # should no longer result in a copy (other than the
    # intermediate copy produced in the assigment)
    y
}

f <- function(x){
    print(tracemem(x))
    x[2] <- 9000.1
    x
}

这里是调用上述函数的一个例子。

> a <- 1:3
> tracemem(a)
[1] "<0x2ac1028>"
> b <- f(forget(a))
[1] "<0x2ac1028>"
[1] "<0x2ac1028>"
tracemem[0x2ac1028 -> 0x2ac1e78]: f 
tracemem[0x2ac1e78 -> 0x308f7a0]: f 
> tracemem(b)
[1] "<0x308f7a0>"
> b
[1]    1.0 9000.1    3.0
> a
Error: object 'a' not found

底线

我正在做我希望我做的事吗?有更好的方法吗?

【问题讨论】:

  • 我很确定有某种rm(x,.GlobalEnv) 的东西,因为许多R 工具可以让您指定执行命令的环境。但是让我问一下:如果x 只在你的函数中使用,你为什么首先将它加载到你的工作区中?为什么不在函数内部创建或加载x
  • 你说得对,可能需要一些背景知识。我有一个数据集,我反复想从中提取两个随机子集:一个设计集x 和一个测试集y。基于设计集,我想定义一个应用于两个集的转换。然后我想将设计集x 发送到用户提供的模型拟合函数f 以获得模型m。然后我想将m 应用于y 并计算性能。由于f 可能会占用大量内存,而x 很大,所以我认为在调用f 之前丢弃我不需要的任何内容会有所帮助。

标签: r memory-management


【解决方案1】:

(1) 环境您可以为此使用环境:

e <- new.env()
e$x <- 1:3
f <- function(e) with(e, x <- x + 1)
f(e)
e$x

(2) 参考类或者因为参考类自动使用环境,所以使用那些:

E <- setRefClass("E", fields = "x",
    methods = list(
        f = function() x <<- x + 1
    )
)
e <- E$new(x = 1:3)
e$f()
e$x

(3) 原型对象也使用环境:

library(proto)
p <- proto(x = 1:3, f = function(.) with(., x <- x + 1))
p$f()
p$x

添加:原型解决方案

更新:将函数名称更改为 f 以与问题保持一致。

【讨论】:

  • 环境功能应该可以做我想要的。我的未知函数f 是否与您的函数myfun 相同,要求提供f 的用户始终将函数体包装在对with 的调用中?这有点不好看,但如果它完成了工作,用户将不得不接受。
  • @Backlin,已将函数名称更改为 f 以与问题保持一致。在 (1) 环境中,要么将 body 包裹在 with 中,如图所示,要么将环境中的组件称为 e$x,以便 f &lt;- function(e) e$x &lt;- e$x + 1 也可以工作。 (2) Reference Classes 中直接引用环境中的变量,如图所示,可以使用.self$x 也可以。在 (3) proto 中,情况与普通环境相同;但是,如果您从不使用继承的方法(例如上面的示例没有使用它们),那么与引用类相同的方法将起作用。
【解决方案2】:

我认为最简单的方法是仅将工作副本加载到内存中,而不是同时加载原始(全局命名空间)和工作副本(函数命名空间)。您可以通过使用“ff”包将“x”和“y”数据集定义为“ffdf”数据框来回避整个问题。据我了解,“ffdf”数据帧驻留在磁盘上,仅在需要部分数据帧时才加载到内存中,并在不再需要这些部分时清除。从理论上讲,这意味着数据将被加载到内存中以复制到函数命名空间中,然后在复制完成后清除。

我承认我很少需要使用 'ff' 包,而且当我这样做时,我通常根本没有任何问题。不过,我并没有检查特定的内存使用情况,而且我的目标通常只是对数据执行大型计算。它有效,我不问问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-12
    • 2020-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多