2

在 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 调用它,这让我避免了不必要的分配),缺乏这种东西似乎导致了显着的性能差异。

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

4

2 回答 2

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"

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

于 2014-08-19T10:00:08.753 回答
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 次;当一个对象被多次引用时,它的引用计数不能减少(因为“多于一个”可能意味着 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(and .Internal, and length) 的编写方式允许在不增加 NAMED 的情况下进行引用;address()应修改为具有类似的行为(现已修复

嗯,当我深入挖掘时,我看到(我想这很明显,回想起来)实际发生的是cumsum(x) 确实通过 S 表达式分配内存

> 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

从更大的内存池开始,因此更少的(初始)垃圾收集。分析您的编码风格以了解为什么您认为这是性能瓶颈会很有用。

于 2014-08-19T13:15:25.563 回答