如何在 R 的 rcpp 中正确更改 ListMatrix 的列表
How to correctly change a list of a ListMatrix in rcpp in R
我想在 rcpp 中更改 ListMatrix 列表的元素,但总是无法做到这一点。请看下面的玩具示例:
library("Rcpp")
cppFunction('
ListMatrix ListMatrixType(ListMatrix x){
NumericMatrix a = x(0,0);
a(0,0) = 100;
return x;
}
')
x = matrix(list(matrix(0,3,2)),2,2)
a = ListMatrixType(x)
a[[1,1]]
a[[2,2]]
我想只有 a[[1,1]
变了,但为什么 a[[2,2]]
也变了?
> a[[1,1]]
[,1] [,2]
[1,] 100 0
[2,] 0 0
[3,] 0 0
> a[[2,2]]
[,1] [,2]
[1,] 100 0
[2,] 0 0
[3,] 0 0
我一定是误解了rcpp中的索引规则。所以我的问题是如何正确更改每个列表的元素?我想每个列表都包含一个矩阵。
我认为你没有误解 Rcpp 中的索引。
不仅a[[1,1]]
和a[[2,2]]
改了,我相信a[[1,2]]
和a[[2,1]]
也改了。
如果我们用另一种方式生成矩阵x
x = matrix(data = c(list(matrix(0,3,2)),
list(matrix(0,3,2)),
list(matrix(0,3,2)),
list(matrix(0,3,2))), 2, 2)
我们会看到 a[[2,2]]
没有改变。
> x = matrix(data = c(list(matrix(0,3,2)),
+ list(matrix(0,3,2)),
+ list(matrix(0,3,2)),
+ list(matrix(0,3,2))), 2, 2)
> a = ListMatrixType(x)
> a[[1,1]]
[,1] [,2]
[1,] 100 0
[2,] 0 0
[3,] 0 0
> a[[2,2]]
[,1] [,2]
[1,] 0 0
[2,] 0 0
[3,] 0 0
TL;DR: 欢迎来到共享环境和 Rcpp 对指针的使用。
有两种方法可以解决 R 在共享环境中对对象的处理,以避免与函数关联的多米诺骨牌更新。
- 执行对象的深拷贝然后更新矩阵列表位置或
- 列表中只有唯一的元素(例如,没有重复的矩阵)
首先,让我们看一下此共享 内存列表的内存分配。为了方便——也为了避免 tracemem()
——我们将使用 lobstr
包来探索共享环境。该软件包目前仅 GitHub,可以使用以下方式获得:
install.packages("devtools")
devtools::install_github("r-lib/lobstr")
有了lobstr
,我们再来看看R...
中矩阵列表的底层内存地址
x = matrix(list(matrix(0,3,2)),2,2)
lobstr::obj_addrs(x)
# [1] "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8"
# ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
# identical memory addresses for all objects
请注意,所有对象共享相同的 内存位置。因此,R 将列表中的每个元素都视为相同。 R 选择此行为以减少内存中数据的大小,因为每个元素都属于相同的 共享环境。
这对于 Rcpp 来说尤其成问题,因为它正在操纵 pointers,例如内存中显示 value 存储位置的位置,以及 not values,例如一个变量,其值类似于 int
或 double
.
由于此 指针 行为,会发生两个操作:
- 矩阵列表中的所有矩阵同时更新,并且
- 整个对象
x
无需 return 语句即可更新。
如果我们稍微修改一下你的函数,第二点就更明显了。
修改函数以说明指针
注意: 这个函数不再有 return 类型,但我们仍然看到 x
对象在 R 中发生变化的环境。
#include<Rcpp.h>
// [[Rcpp::export]]
void ListMatrixType_pointer(Rcpp::ListMatrix x){
Rcpp::NumericMatrix a = x(0, 0);
a(0, 0) = 100;
}
输出
ListMatrixType_pointer(x)
str(x)
# List of 4
# $ : num [1:3, 1:2] 100 0 0 0 0 0
# $ : num [1:3, 1:2] 100 0 0 0 0 0
# $ : num [1:3, 1:2] 100 0 0 0 0 0
# $ : num [1:3, 1:2] 100 0 0 0 0 0
# - attr(*, "dim")= int [1:2] 2 2
注意,我们不是必须return一个值,因为x
是自动更新的。此外,我们仍然有每个元素的相同内存位置,例如
lobstr::obj_addrs(x)
# [1] "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8"
# ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
# identical memory addresses for all objects
矩阵的深层副本
要绕过相同的内存地址,您可以深度克隆对象,然后将其保存回 ListMatrix
。 clone()
函数实例化一个新的内存块来存储值。因此,修改一个元素不再会触发多米诺骨牌更新。此外,当您将克隆的对象添加回列表时,内存地址仅针对该元素发生变化。
使用clone()
进行深拷贝
#include<Rcpp.h>
// [[Rcpp::export]]
void ListMatrixType_clone(Rcpp::ListMatrix x){
Rcpp::NumericMatrix a = x(0, 0);
// Perform a deep copy of a into b
// and, thus, changing the memory address
Rcpp::NumericMatrix b = Rcpp::clone(a);
b(0, 0) = 100;
// Update x with b
x(0, 0) = b;
}
输出
ListMatrixType_clone(x)
str(x)
# List of 4
# $ : num [1:3, 1:2] 100 0 0 0 0 0
# $ : num [1:3, 1:2] 0 0 0 0 0 0
# $ : num [1:3, 1:2] 0 0 0 0 0 0
# $ : num [1:3, 1:2] 0 0 0 0 0 0
# - attr(*, "dim")= int [1:2] 2 2
lobstr::obj_addrs(x)
# [1] "0x7fceb811a7e8" "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8"
# ^^^^^^^ ^^^^^^^
# different memory addresses
列表中的唯一元素
为了强调唯一元素的需要,考虑修改矩阵列表第一个位置的矩阵,只有第一个元素的内存地址会改变;其余地址将保持不变。例如
x[[1, 1]] = matrix(1, 2, 2)
lobstr::obj_addrs(x)
# [1] "0x7fceb811a7e8" "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8"
# ^^^^^^^ ^^^^^^^
# different memory addresses
其他资源
有关此主题的进一步阅读,请参阅下面任何强调与 Rcpp 相关的指针行为的链接帖子(免责声明: I写给他们):
- Declare a variable as a reference in Rcpp
关于对象大小的杂项说明
注意:Base R 中 utils
中的 object.size()
提供了共享环境大小的错误计算。 c.f。 ?utils::object.size
This function merely provides a rough indication: it should be reasonably accurate for atomic vectors, but does not detect if elements of a list are shared, for example. (Sharing amongst elements of a character vector is taken into account, but not that between character vectors in a single object.)
lobstr::obj_size(x)
# 424 B
utils::object.size(x)
# 1304 bytes
因此,由于共享环境,列表的对象大小约为 1/3。
我想在 rcpp 中更改 ListMatrix 列表的元素,但总是无法做到这一点。请看下面的玩具示例:
library("Rcpp")
cppFunction('
ListMatrix ListMatrixType(ListMatrix x){
NumericMatrix a = x(0,0);
a(0,0) = 100;
return x;
}
')
x = matrix(list(matrix(0,3,2)),2,2)
a = ListMatrixType(x)
a[[1,1]]
a[[2,2]]
我想只有 a[[1,1]
变了,但为什么 a[[2,2]]
也变了?
> a[[1,1]]
[,1] [,2]
[1,] 100 0
[2,] 0 0
[3,] 0 0
> a[[2,2]]
[,1] [,2]
[1,] 100 0
[2,] 0 0
[3,] 0 0
我一定是误解了rcpp中的索引规则。所以我的问题是如何正确更改每个列表的元素?我想每个列表都包含一个矩阵。
我认为你没有误解 Rcpp 中的索引。
不仅a[[1,1]]
和a[[2,2]]
改了,我相信a[[1,2]]
和a[[2,1]]
也改了。
如果我们用另一种方式生成矩阵x
x = matrix(data = c(list(matrix(0,3,2)),
list(matrix(0,3,2)),
list(matrix(0,3,2)),
list(matrix(0,3,2))), 2, 2)
我们会看到 a[[2,2]]
没有改变。
> x = matrix(data = c(list(matrix(0,3,2)),
+ list(matrix(0,3,2)),
+ list(matrix(0,3,2)),
+ list(matrix(0,3,2))), 2, 2)
> a = ListMatrixType(x)
> a[[1,1]]
[,1] [,2]
[1,] 100 0
[2,] 0 0
[3,] 0 0
> a[[2,2]]
[,1] [,2]
[1,] 0 0
[2,] 0 0
[3,] 0 0
TL;DR: 欢迎来到共享环境和 Rcpp 对指针的使用。
有两种方法可以解决 R 在共享环境中对对象的处理,以避免与函数关联的多米诺骨牌更新。
- 执行对象的深拷贝然后更新矩阵列表位置或
- 列表中只有唯一的元素(例如,没有重复的矩阵)
首先,让我们看一下此共享 内存列表的内存分配。为了方便——也为了避免 tracemem()
——我们将使用 lobstr
包来探索共享环境。该软件包目前仅 GitHub,可以使用以下方式获得:
install.packages("devtools")
devtools::install_github("r-lib/lobstr")
有了lobstr
,我们再来看看R...
x = matrix(list(matrix(0,3,2)),2,2)
lobstr::obj_addrs(x)
# [1] "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8"
# ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
# identical memory addresses for all objects
请注意,所有对象共享相同的 内存位置。因此,R 将列表中的每个元素都视为相同。 R 选择此行为以减少内存中数据的大小,因为每个元素都属于相同的 共享环境。
这对于 Rcpp 来说尤其成问题,因为它正在操纵 pointers,例如内存中显示 value 存储位置的位置,以及 not values,例如一个变量,其值类似于 int
或 double
.
由于此 指针 行为,会发生两个操作:
- 矩阵列表中的所有矩阵同时更新,并且
- 整个对象
x
无需 return 语句即可更新。
如果我们稍微修改一下你的函数,第二点就更明显了。
修改函数以说明指针
注意: 这个函数不再有 return 类型,但我们仍然看到 x
对象在 R 中发生变化的环境。
#include<Rcpp.h>
// [[Rcpp::export]]
void ListMatrixType_pointer(Rcpp::ListMatrix x){
Rcpp::NumericMatrix a = x(0, 0);
a(0, 0) = 100;
}
输出
ListMatrixType_pointer(x)
str(x)
# List of 4
# $ : num [1:3, 1:2] 100 0 0 0 0 0
# $ : num [1:3, 1:2] 100 0 0 0 0 0
# $ : num [1:3, 1:2] 100 0 0 0 0 0
# $ : num [1:3, 1:2] 100 0 0 0 0 0
# - attr(*, "dim")= int [1:2] 2 2
注意,我们不是必须return一个值,因为x
是自动更新的。此外,我们仍然有每个元素的相同内存位置,例如
lobstr::obj_addrs(x)
# [1] "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8"
# ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
# identical memory addresses for all objects
矩阵的深层副本
要绕过相同的内存地址,您可以深度克隆对象,然后将其保存回 ListMatrix
。 clone()
函数实例化一个新的内存块来存储值。因此,修改一个元素不再会触发多米诺骨牌更新。此外,当您将克隆的对象添加回列表时,内存地址仅针对该元素发生变化。
使用clone()
进行深拷贝
#include<Rcpp.h>
// [[Rcpp::export]]
void ListMatrixType_clone(Rcpp::ListMatrix x){
Rcpp::NumericMatrix a = x(0, 0);
// Perform a deep copy of a into b
// and, thus, changing the memory address
Rcpp::NumericMatrix b = Rcpp::clone(a);
b(0, 0) = 100;
// Update x with b
x(0, 0) = b;
}
输出
ListMatrixType_clone(x)
str(x)
# List of 4
# $ : num [1:3, 1:2] 100 0 0 0 0 0
# $ : num [1:3, 1:2] 0 0 0 0 0 0
# $ : num [1:3, 1:2] 0 0 0 0 0 0
# $ : num [1:3, 1:2] 0 0 0 0 0 0
# - attr(*, "dim")= int [1:2] 2 2
lobstr::obj_addrs(x)
# [1] "0x7fceb811a7e8" "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8"
# ^^^^^^^ ^^^^^^^
# different memory addresses
列表中的唯一元素
为了强调唯一元素的需要,考虑修改矩阵列表第一个位置的矩阵,只有第一个元素的内存地址会改变;其余地址将保持不变。例如
x[[1, 1]] = matrix(1, 2, 2)
lobstr::obj_addrs(x)
# [1] "0x7fceb811a7e8" "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8"
# ^^^^^^^ ^^^^^^^
# different memory addresses
其他资源
有关此主题的进一步阅读,请参阅下面任何强调与 Rcpp 相关的指针行为的链接帖子(免责声明: I写给他们):
- Declare a variable as a reference in Rcpp
关于对象大小的杂项说明
注意:Base R 中 utils
中的 object.size()
提供了共享环境大小的错误计算。 c.f。 ?utils::object.size
This function merely provides a rough indication: it should be reasonably accurate for atomic vectors, but does not detect if elements of a list are shared, for example. (Sharing amongst elements of a character vector is taken into account, but not that between character vectors in a single object.)
lobstr::obj_size(x)
# 424 B
utils::object.size(x)
# 1304 bytes
因此,由于共享环境,列表的对象大小约为 1/3。