R可以像现场一样进行操作吗?

时间:2014-08-19 09:18:38

标签: r memory matrix cumsum

在Python中,我可以这样做:

a = np.arange(100)
print id(a) # shows some number
a[:] = np.cumsum(a)
print(id(a)) # shows the same number

我在这里做的是用它的cumsum替换a内容。之前和之后的地址是相同的。

现在让我们在R中尝试:

install.packages('pryr')
library(pryr)
a = 0:99
print(address(a)) # shows some number
a[1:length(a)] = cumsum(a)
print(address(a)) # shows a different number!

问题是如何用计算结果覆盖R中已经分配的内存?当我在R与Rcpp中进行向量运算时(在C ++中编写代码并从R调用它,这样可以避免不必要的分配),缺少这种东西似乎会导致显着的性能差异。

我在Ubuntu Linux 10.04上使用R 3.1.1,具有24个物理内核和128 GB RAM。

2 个答案:

答案 0 :(得分:6)

试用data.table套餐。它允许使用:=运算符(以及使用函数set)通过引用更新值

library(data.table)
A <- data.table(a = seq_len(99))

address(A)   # [1] "0x108d283f0"
address(A$a) # [1] "0x108e548a0"

options(datatable.verbose=TRUE)
A[, a := cumsum(a)]
# Detected that j uses these columns: a 
# Assigning to all 99 rows
# Direct plonk of unnamed RHS, no copy. <~~~ no copy of `A` or `A$a` is made.

address(A)   # [1] "0x108d283f0"
address(A$a) # [1] "0x1078f5070"

请注意,即使A$a地址在通过引用进行更新后有所不同,也会在此处进行无复制。它有所不同,因为它是一个完整的列plonk - 意味着向量cumsum(a)替换当前列a(通过引用)。 (您看到的地址基本上是cumsum(a)的地址。)

答案 1 :(得分:4)

我做了这个

> x = 1:5
> .Internal(inspect(x))
@3acfed60 13 INTSXP g0c3 [NAM(1)] (len=5, tl=0) 1,2,3,4,5
> x[] = cumsum(x)
> .Internal(inspect(x))
@3acfed60 13 INTSXP g0c3 [NAM(1)] (len=5, tl=0) 1,3,6,10,15

其中@3acfed60是(共享)内存地址。关键是NAM(1),它表示只有一个x的引用,因此无需在更新时重新分配。

R使用(目前,我认为这将在下一版本中更改)一个引用计数版本,其中R符号为引用0,1或1次以上;当一个对象被多次引用时,它的引用计数不能递减(因为&#39;多于一个&#39;可能意味着3,因此没有办法区分2个引用和3个引用,因此没办法区分小于2和小于3的一个。任何修改尝试都需要复制。

最初我忘记加载pryr并编写自己的address()

> address = function(x) .Internal(inspect(x))

揭示了一个有趣的问题

> x = 1:5
> address(x)
@4647128 13 INTSXP g0c3 [NAM(2)] (len=5, tl=0) 1,2,3,4,5
> x[] = cumsum(x)
> address(x)
@4647098 13 INTSXP g0c3 [NAM(2)] (len=5, tl=0) 1,3,6,10,15

注意NAM(2),它表示在函数内部至少有两个对x的引用,即在全局环境中和函数环境中。因此,触摸函数内的x会触发未来的重复,这是海森堡不确定性原理的一种。 cumsum(以及.Internallength)的编写方式允许无增量地引用NAMED; address()应该修改为具有类似行为(现在已经是fixed

嗯,当我深入挖掘时,我发现(我想它很明显,回想起来)实际发生的事情是cumsum(x) 通过S分配内存-expression

> x = 1:5
> .Internal(inspect(x))
@3bb1cd0 13 INTSXP g0c3 [NAM(1)] (len=5, tl=0) 1,2,3,4,5
> .Internal(inspect(cumsum(x)))
@43919d0 13 INTSXP g0c3 [] (len=5, tl=0) 1,3,6,10,15

但是赋值x[] <-将新内存与旧位置(??)相关联。 (这似乎和data.table一样高效,显然也为cumsum创建了一个S表达式,大概是因为它本身就是调用cumsum!)所以大多数情况下我都没有在这个答案中有所帮助...

分配本身不太可能导致性能问题,而是不再使用内存的垃圾收集(gcinfo(TRUE)来查看这些)。我觉得用

启动R很有用
R --no-save --quiet --min-vsize=2048M --min-nsize=45M

以更大的内存池开始,因此更少(初始)垃圾收集。分析您的编码风格以理解为什么您将其视为性能瓶颈将是有用的。