【问题标题】:R: preventing copies when passing a variable into a functionR:将变量传递给函数时防止复制
【发布时间】:2017-07-24 06:14:00
【问题描述】:

Hadley 的新 pryr 包显示了变量的地址,非常适合分析。我发现,每当将变量传递给函数时,无论该函数做什么,都会创建该变量的副本。此外,如果函数体将变量传递给另一个函数,则会生成另一个副本。这是一个明显的例子

n = 100000
p = 100

bar = function(X) {
  print(pryr::address(X))
}

foo = function(X) {
  print(pryr::address(X))
  bar(X)
}

X = matrix(rnorm(n*p), n, p)
print(pryr::address(X))
foo(X)

生成

> X = matrix(rnorm(n*p), n, p)
> print(pryr::address(X))
[1] "0x7f5f6ce0f010"
> foo(X)
[1] "0x92f6d70"
[1] "0x92f3650"

地址每次都会改变,尽管函数没有做任何事情。我对这种行为感到困惑,因为我听说 R 被描述为写入时复制 - 因此可以传递变量,但只有在函数想要写入该变量时才会生成副本。这些函数调用发生了什么?

为了最好的 R 开发,最好不要编写多个小函数,而是将内容全部放在一个函数中?我还在Reference Classes 上找到了一些讨论,但我看到很少有 R 开发人员使用它。还有另一种有效的方法来传递我缺少的变量吗?

【问题讨论】:

  • 在我看来,这是R 最丑陋的缺点之一。您可以尝试使用environment 来解决此问题。例如,请参阅讨论 here
  • pryr 包不是新的。

标签: r


【解决方案1】:

我不完全确定,但是地址可能指向指向对象的指针的内存地址。举个例子。

library(pryr)
n <- 100000
p <- 500
X <- matrix(rep(1,n*p), n, p)
l <- list()
for(i in 1:10000) l[[i]] <- X

此时,如果l 的每个元素都是X 的副本,则l 的大小约为3.5Tb。显然情况并非如此,因为您的计算机会开始冒烟。但是地址不同。

sapply(l[1:10], function(x) address(x))
# [1] "0x1062c14e0" "0x1062c0f10" "0x1062bebc8" "0x10641e790" "0x10641dc28" "0x10641c640" "0x10641a800" "0x1064199c0"
# [9] "0x106417380" "0x106411d40"

【讨论】:

    【解决方案2】:

    pryr::address 将未计算的符号传递给内部函数,该函数在 parent.frame() 中返回其地址:

    pryr::address
    #function (x) 
    #{
    #    address2(check_name(substitute(x)), parent.frame())
    #}
    #<environment: namespace:pryr>
    

    上述函数的包装可能会导致“承诺”的返回地址。为了说明,我们可以模拟pryr::address的功能为:

    ff = inline::cfunction(sig = c(x = "symbol", env = "environment"), body = '
        SEXP xx = findVar(x, env);
    
        Rprintf("%s at %p\\n", type2char(TYPEOF(xx)), xx);
    
        if(TYPEOF(xx) == PROMSXP) {
            SEXP pr = eval(PRCODE(xx), PRENV(xx));
            Rprintf("\tvalue: %s at %p\\n", type2char(TYPEOF(pr)), pr);
        }
    
        return(R_NilValue);
    ') 
    wrap1 = function(x) ff(substitute(x), parent.frame())
    

    其中wrap1 等同于pryr::address

    现在:

    x = 1:5
    
    .Internal(inspect(x))
    #@256ba60 13 INTSXP g0c3 [NAM(1)] (len=5, tl=0) 1,2,3,4,5
    
    pryr::address(x)
    #[1] "0x256ba60"
    
    wrap1(x)
    #integer at 0x0256ba60
    #NULL
    

    通过进一步的包装,我们可以看到一个“promise”对象正在被构造,而值没有被复制:

    wrap2 = function(x) wrap1(x)
    
    wrap2(x)
    #promise at 0x0793f1d4
    #        value: integer at 0x0256ba60
    #NULL
    
    wrap2(x)
    #promise at 0x0793edc8
    #        value: integer at 0x0256ba60
    #NULL
    
    # wrap 'pryr::address' like your 'bar'   
    ( function(x) pryr::address(x) )(x) 
    #[1] "0x7978a64"
    
    ( function(x) pryr::address(x) )(x)
    #[1] "0x79797b8"
    

    【讨论】:

      【解决方案3】:

      您可以使用profmem 包(我是作者)来查看发生了哪些内存分配。它要求您的 R 会话使用“profmem”功能构建:

      capabilities()["profmem"]
      ## profmem 
      ## TRUE
      

      然后,你可以这样做:

      n <- 100000
      p <- 100
      X <- matrix(rnorm(n*p), nrow = n, ncol = p)
      object.size(X)
      ## 80000200 bytes
      
      ## No copies / no new objects
      bar <- function(X) X
      foo <- function(X) bar(X)
      
      ## One new object
      bar2 <- function(X) 2*X
      foo2 <- function(X) bar2(X)
      
      profmem::profmem(foo(X))
      ## Rprofmem memory profiling of:
      ## foo(X)
      ## 
      ## Memory allocations:
      ##       bytes calls
      ## total     0    
      
      profmem::profmem(foo2(X))
      ## Rprofmem memory profiling of:
      ## foo2(X)
      ## 
      ## Memory allocations:
      ##          bytes            calls
      ## 1     80000040 foo2() -> bar2()
      ## total 80000040    
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-10-30
        • 2011-02-26
        • 2013-10-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-18
        相关资源
        最近更新 更多