【问题标题】:Avoid copying the whole vector when replacing an element (a[1] <- 2)替换元素时避免复制整个向量 (a[1] <- 2)
【发布时间】:2013-08-21 14:17:05
【问题描述】:

当替换向量中的元素时,例如

a <- 1:1000000
a[1] <- 2

R 复制整个向量,替换新向量中的元素,然后进行变量名重新关联。我想知道是否要覆盖或阻止它以使其行为更像 c 数组?

谢谢

【问题讨论】:

  • @沉:欢迎来到SO。你能提供reproducible example吗?
  • 只是添加一个例子,谢谢
  • @Metrics 我认为 OP 是说接下来的步骤,子分配 ?`[&lt;-`,制作了 a 的完整副本。我没有看到文档中提到过,但听起来很熟悉。
  • @Metrics 抱歉,我没有说清楚。问题不在于输出,而在于内部内存管理。似乎 R 会在修改之前制作一个内部副本,这会减慢代码的速度。您可以使用 system.time(a[1]

标签: r


【解决方案1】:

tracemem 函数(需要编译 R 以支持它)提供了复制发生时间的指示。这就是你所做的

> a <- 1:1000000; tracemem(a)
[1] "<0x7f791b39e010>"
> a[1] = 2
tracemem[0x7f791b39e010 -> 0x7f791a9d4010]: 

确实有一个副本。但这是因为您将 a 从一个整数向量(1:1000000 创建一个整数序列)强制转换为一个数字向量(因为 2 是一个数值,而 R 强制转换为一个通用类型)。相反,如果您使用整数值更新整数向量,或者使用数值更新数字向量,则不会复制

> a <- 1:1000000; tracemem(a)
[1] "<0x7f791a4ef010>"
> a[1] = 2L
> a = c(1, 2, 3); tracemem(a)
[1] "<0x5180470>"
> a[1] = 2
>

更深入的了解来自对 R 的内存管理工作原理的肤浅理解。每个分配都有一个与之关联的 NAMED 级别。 NAMED=0 或 1 表示最多有 1 个符号引用它;因此,原地复制是安全的。 NAMED=2 表示存在或已经存在至少 2 个符号指向同一位置,并且任何更新值的尝试都需要复制以保持 R 的“更改时复制”的错觉。下面揭示了a 的一些内部结构,包括它的类型为 INTSXP(整数)和 NAM(1)(命名级别 1),并且它正在被跟踪。因此更新(使用整数!)不需要副本。

> a = 1:10; tracemem(a); .Internal(inspect(a))
[1] "<0x5170818>"
@5170818 13 INTSXP g0c4 [NAM(1),TR] (len=10, tl=0) 1,2,3,4,5,...
> a[1] = 2L
> 

另一方面,这里有两个符号指的是内存中的位置,因此 NAMED 是 2 并且需要一个副本

> a = b = 1:10; tracemem(a); .Internal(inspect(a))
[1] "<0x576d1a0>"
@576d1a0 13 INTSXP g0c4 [NAM(2),TR] (len=10, tl=0) 1,2,3,4,5,...
> a[1] = 2L
tracemem[0x576d1a0 -> 0x576d148]: 

NAMED 很难推理,所以在某种程度上,这些类型的游戏对它们来说是徒劳的。

inspect 返回其他信息。每个 R 类型在内部都表示为“SEXP”(S 表达式)类型。这些是枚举的,第 13 个 SEXP 类型是一个整数 SEXP——因此是13 INTSXP。查看.Internal(inspect(...)) 以获取数字向量、字符向量甚至函数.Internal(inspect(function() {}))

R 通过定期运行“垃圾收集器”来管理内存,该“垃圾收集器”检查当前是否引用了内存;如果不是,则将其回收以供另一个符号使用。垃圾收集器是“分代的”,这意味着最近分配的内存比旧内存更频繁地检查回收(这是因为,根据经验,变量的半衰期往往很短,例如,在函数调用期间,所以最近分配的内存比使用时间更长的内存更有可能用于回收)。 g0c4 和类似的注释提供了有关 SEXP 所属代的信息。

TR 代表 SEXP 中设置的“位”,表示正在跟踪变量;它是在我们说tracemem(a)时设置的。

其中一些主题在 R 的内部实现文档RShowDoc("R-ints") 和 C 头文件 Rinternals.h 中进行了讨论。

【讨论】:

  • 谢谢!非常有趣和有帮助!我想知道检查输出中的 13 g0c4 TR 和 tl 代表什么?
  • @Shen 我在答案中添加了一个关于此的部分,并引用了 R 内部手册和源代码。
  • 谢谢!非常感谢!
【解决方案2】:

您可以使用 CRAN 上的 ff 包来执行此操作。使用 ff,您的数据存储在磁盘上,索引只会影响您正在索引的特定元素

require(ff)
a <- ff(1:1000000)
a[1] <- 2

信息。这些是时间安排,因此对于您的玩具箱来说要快得多。

require(ff)
a <- 1:100000000
b <- ff(a)
system.time(a[1] <- 2)
 user  system elapsed 
0.440   0.592   1.056 
system.time(b[1] <- 2)
 user  system elapsed 
0.004   0.000   0.001 

【讨论】:

  • 哦!有趣的!在这种情况下,磁盘的 IO 是否会使其变慢?
  • @Shen 如果您加载了microbenchmark 包,则可以轻松针对您的特定代码变体运行时序测试。
  • 取决于您的硬件,但速度非常快。为您的玩具示例添加了时间。
  • 谢谢!这很有帮助!我会调查的!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-28
  • 2018-12-23
  • 2017-09-26
  • 2016-08-16
  • 2020-09-16
  • 2018-07-23
相关资源
最近更新 更多