在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。
答案 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
(以及.Internal
和length
)的编写方式允许无增量地引用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 --no-save --quiet --min-vsize=2048M --min-nsize=45M
以更大的内存池开始,因此更少(初始)垃圾收集。分析您的编码风格以理解为什么您将其视为性能瓶颈将是有用的。