R:在将变量传递给函数时阻止复制

时间:2017-03-04 00:37:16

标签: r

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开发人员使用它。是否有另一种有效的方法来传递我遗漏的变量?

3 个答案:

答案 0 :(得分:5)

我不完全确定,但地址可能指向指向对象的指针的内存地址。以下面的例子为例。

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"

答案 1 :(得分:4)

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"

答案 2 :(得分:3)

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

add_filter('get_comment_author', 'my_comment_author', 10, 1);
function my_comment_author( $author = '' ) {
// Get the comment ID from WP_Query
$comment = get_comment( $comment_ID );
if (!empty($comment->comment_author) ) {
if($comment->user_id > 0){
$user=get_userdata($comment->user_id);
$author=$user->first_name.' '.substr($user->last_name,0,1).'.'; // this is the actual line you want to change
} else {
$author = __('Anonymous');
}
} else {
$author = $comment->comment_author;
}

return $author;
}

然后,你可以这样做:

data.table