当替换向量中的元素时,例如
a <- 1:1000000
a[1] <- 2
R 复制整个向量,替换新向量中的元素,然后进行变量名重新关联。无论如何,我想知道是否要覆盖或阻止它以使其表现得更像 c 数组?
谢谢
当替换向量中的元素时,例如
a <- 1:1000000
a[1] <- 2
R 复制整个向量,替换新向量中的元素,然后进行变量名重新关联。无论如何,我想知道是否要覆盖或阻止它以使其表现得更像 c 数组?
谢谢
该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)(NAMED 级别 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 所属代的信息。
表示 SEXP 中设置的TR
“位”,以指示正在跟踪变量;我们说的时候就设置好了tracemem(a)
。
其中一些主题在 R 的内部实现文档RShowDoc("R-ints")
和 C 头文件 Rinternals.h 中进行了讨论。
您可以使用 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