【问题标题】:Can R do operations like cumsum in-place?R 可以就地执行 cumsum 之类的操作吗?
【发布时间】:2014-10-12 08:06:29
【问题描述】:

在 Python 中我可以这样做:

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

我在这里所做的是将 acontents 替换为其 cumsum。前后地址相同。

现在让我们在 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。

【问题讨论】:

  • R 中的所有内容都是副本,除非您使用data.table
  • @James,这不准确。 R 在某些情况下避免复制时有一些copy-on-modify 规则。见here
  • @James,例如尝试a = 0:99 ;print(address(a)) ;b <- a ; print(address(b))
  • @DavidArenburg 非常有趣,谢谢。
  • 补充一点 David 的观点,R 3.1+ 避免了深拷贝,而是在许多地方进行了 拷贝(这极大地提高了性能)。

标签: r memory matrix cumsum


【解决方案1】:

试试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)的地址)。

【讨论】:

  • 你取的是A的地址;查看A$a的地址。
  • @MartinMorgan,进行了编辑以澄清事情。由于整列 plonk,地址会有所不同,但不会进行 复制
  • 谢谢@Arun。我还认为,为了说明您的观点,证明address(A) 没有改变(不像,例如,Adata.frame)仍然很有用。
  • 我猜 data.table 调用 R 的 cumsum,它在 data.table 和从全局环境调用时创建一个新向量,因此使用 data.table 对内存管理的这方面没有帮助。 @Arun 关于改进基础 R 中的内存管理的观点的一个说明是,如果 A 是一个数据框并且有第二列 b,则 b 不会被复制(而在以前的 R 版本中它会复制)。
  • @MarginMorgan,不是真的。来自j-expression (a := cumsum(a)) 的 RHS (cumsum(a)) 被提取并评估。然后分配回x。仅创建一次新向量(在您的情况下也无法避免)。但是,与您的答案相比,我确实看到内存使用量增加了,与“a”的大小相同(在相当大的数据 ~1.5Gb 上测试)。这很可能是一个错误(因为它根本与 cumsum 部分无关。发生了不必要的 dt 列副本)。
【解决方案2】:

我做了这个

> 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(和.Internal,和length)的编写方式允许引用而不增加NAMED; address() 应该修改为具有类似的行为(现在是 fixed

嗯,当我深入挖掘时,我发现(我想这很明显,回想起来)实际发生的是 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[] &lt;- 将新内存与旧位置关联(??)。 (这似乎与 data.table 一样“高效”,它显然也为 cumsum 创建了一个 S 表达式,大概是因为它本身调用了 cumsum!)所以大多数情况下我对这个答案没有帮助......

分配本身不太可能导致性能问题,而是不再使用的内存的垃圾收集(gcinfo(TRUE) 看到这些)。我发现使用启动 R 很有用

R --no-save --quiet --min-vsize=2048M --min-nsize=45M

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-28
    • 1970-01-01
    • 2016-11-03
    相关资源
    最近更新 更多