【问题标题】:How to correctly change a list of a ListMatrix in rcpp in R如何正确更改 R 中 rcpp 中的 ListMatrix 列表
【发布时间】:2018-07-07 15:53:23
【问题描述】:

我想在 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中的索引规则。所以我的问题是如何正确更改每个列表的元素?我想每个列表都包含一个矩阵。

【问题讨论】:

    标签: r indexing rcpp


    【解决方案1】:

    TL;DR:欢迎来到共享环境和 Rcpp 对指针的使用。

    有两种方法可以解决 R 在共享环境中对对象的处理,以避免与函数相关联的多米诺骨牌更新。

    1. 执行对象的深拷贝,然后更新矩阵列表位置或
    2. 列表中只有唯一元素(例如,没有重复的矩阵)

    首先,让我们看看这个共享内存列表的内存分配。为方便起见——并避免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 来说尤其成问题,因为它正在操纵 指针,例如内存中的位置,显示 value 的存储位置,not values,例如一个包含 intdouble 之类的值的变量。

    由于这种指针行为,会发生两个动作:

    1. 矩阵列表中的所有矩阵同时更新,并且
    2. 整个对象x 无需返回语句即可更新。

    如果我们稍微修改一下你的函数,第二点就更明显了。

    修改了说明指针的函数

    注意:这个函数不再有返回类型,但我们仍然看到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
    

    注意,我们不必必须返回一个值,因为x 会自动更新。此外,对于每个元素,我们仍然拥有相同的内存位置,例如

    lobstr::obj_addrs(x)
    # [1] "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8" "0x7fceb69757c8"
    #      ^^^^^^^^^^^^^^   ^^^^^^^^^^^^^^ 
    # identical memory addresses for all objects
    

    矩阵的深拷贝

    要绕过相同的内存地址,您可以深度克隆对象,然后将其保存回ListMatrixclone() 函数实例化一个新的内存块来存储值。因此,修改一个元素不再会触发多米诺骨牌更新。此外,当您将克隆对象添加回列表时,仅该元素的内存地址会更改。

    使用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 相关的指针行为的链接帖子(免责声明:我写了它们):

    关于对象大小的其他说明

    注意:Base R 中 utils 中的 object.size() 提供了对共享环境大小的错误计算。参考文献?utils::object.size

    这个函数只是提供了一个粗略的指示:它对于原子向量应该是相当准确的,但不检测列表的元素是否共享,例如。 (考虑了字符向量元素之间的共享,但不考虑单个对象中字符向量之间的共享。)

    lobstr::obj_size(x)
    # 424 B
    utils::object.size(x)
    # 1304 bytes
    

    所以,由于共享环境,列表的对象大小约为1/3。

    【讨论】:

    • 非常感谢您的清晰启发式解释!很高兴知道 R 的隐式行为以及如何在 Rcpp 中克服这个问题:)
    【解决方案2】:

    我认为您没有误解 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
    

    【讨论】:

    • 非常感谢您的回答。我相信@coatless 的解释还包括您的回答,作为克服我的问题的一种方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-04
    • 2021-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多