R6: 序列化 R6 类?

创建于 2018-09-19  ·  8评论  ·  资料来源: r-lib/R6

你好。
抄送@leobelen
我们正在开发一个包Rpolyhedra 。 Rpolyhedra 是从互联网公共可用资源中抓取的多面体数据库。
它从抓取的来源制作多面体 R6 对象。 抓取过程的分类帐和数据库本身是 R6 对象。

然后我们将所有内容都放在一个 RDS 文件中,用于加速、回归测试、设置数据库的预抓取完整版本以及其他功能。

@wch要求我们不要在 RDS 文件中包含 R6 对象,因为最终用户计算机中安装的不同 R6 版本之间不兼容。

我们认为解决方案是在将 R6 对象保存到 RDS 时对其进行序列化。 因此,具有不同 R6 版本的用户可以访问相同的 RDS,而不会出现不兼容的风险。

我阅读了 R6 代码,评估如何应用元编程来访问对象的字段(状态)对我来说并不简单,更不用说找出如何保证序列化/反序列化是安全的。

我们开始用我们自己的序列化方法来做它,但想知道是否有共识,这个功能的路线图将由包本身解决。

我看到有一个方法as.list.R6 #91 并且在内部使用了get_nonfunctions可能很有用。 但不确定我们是否能够提出一个奇特且社区满意的解决方案。 如果对提议的功能的价值存在共识,那么这个问题应该由精通 R6 哲学的开发人员解决。

feature

所有8条评论

R6 特别注意使存储的 R6 对象即使在具有不同版本的 R6 的计算机上也正常运行。 但是,不能保证对象本身在不同 R6 版本的结构上完全相同。

Rpolyhedra (qbotics/Rpolyhedra#21) 中的问题是您将存储的 R6 对象与动态生成的对象进行比较。 存储的对象可能是在具有不同 R6 版本的不同机器上创建的,因此结果对象可能与动态对象不同,即使所有输入都相同。 在那个问题中,R6 中的clone()方法发生了变化,这就是导致比较失败的原因。

我认为您需要具体说明您对该序列化的期望。 serialize()函数将序列化对象(它是saveRDS()使用的),但由于我上面描述的原因,它不适合您的目的。

@wch我一直在研究基于 R6 的包,这要求我在某些点保存对象的“状态”。 一些对象与其他对象中的 R6 对象列表完全嵌套在一起。 创建时,内存中的大小以 10 兆 MB 为单位。 当保存(通过savesaveRDSserialize )然后重新加载时,内存需求会爆炸到> GB。 调试和分析以及pryr::inspect建议(至少对我而言),正在为每个实例创建附加到每个 R6 对象实例的函数。 我相信这与 Ropensci/drake#383 有关。 关于 R6 类的更好/解决方法保存方法的任何想法?

@wch我们及时考虑了您的观察并实施了绕过问题的解决方案。

@d-sharpe 对不起,我不知道如何帮助你。

让我们等待@wch 的回答。

@d-sharpe 如果您能提供一个可重现的小示例,那将有助于我准确了解您所面临的问题。

@wch感谢您的回复。 下面的例子似乎说明了我所看到的。 我看到更多 _expansion_ 附加到类的更多功能以及这些功能的更多复杂性。

library(R6)

classA <-
  R6Class(
    classname = "classA",
    class = TRUE,
    cloneable = FALSE,
    public = list(
      initialize = function(n = 100) {
        private$collectionOfB <-
          lapply(seq_len(n), function(x) {
            classB$new(n = n)
          })
      },
      getInstanceOfB = function(item) {
        return(private$collectionOfB[[item]])
      },
      getNumberOfInstances = function(...) {
        return(length(private$collectionOfB))
      },
      fun1 = function(...) {
        return(runif(1))
      }
    ),
    private = list(collectionOfB = list())
  )

classB <-
  R6Class(
    classname = "classB",
    class = TRUE,
    cloneable = FALSE,
    public = list(
      initialize = function(n = 100) {
        private$collectionOfC <-
          lapply(seq_len(n), function(x) {
            classC$new(n = n)
          })
      },
      getInstanceOfC = function(item) {
        return(private$collectionOfC[[item]])
      },
      getNumberOfInstances = function(...) {
        return(length(private$collectionOfC))
      },
      fun1 = function(...) {
        return(runif(1))
      }
    ),
    private = list(collectionOfC = list())
  )

classC <-
  R6Class(
    classname = "classC",
    class = TRUE,
    cloneable = FALSE,
    public = list(
      initialize = function(n = 20) {
        private$values <- rnorm(n)
      },
      getValues = function(...) {
        return(private$values)
      },
      fun1 = function(...) {
        return(runif(1))
      },
      fun2 = function(...) {
        return(runif(1))
      },
      fun3 = function(...) {
        return(runif(1))
      },
      fun4 = function(...) {
        return(runif(1))
      },
      fun5 = function(...) {
        return(runif(1))
      }
    ),
    private = list(values = numeric(0L))
  )


x <- classA$new()

library(pryr)

object_size(x)
## 25.6 MB

x_copy <-
  unserialize(serialize(x, connection = NULL))

object_size(x_copy)
## 146 MB

我认为大小增加可能是因为serializeunserialize不够聪明,无法对相同函数的组件(即主体和形式)进行重复数据删除。 例如:

这是一个简单的例子(没有 R6),说明:

x <- lapply(1:1000, function(i) {
  function() i
})

object_size(x)
#> 355 kB

x_copy <- unserialize(serialize(x, version = 3, connection = NULL))
object_size(x_copy)
#> 2.07 MB

感谢您查看此@wch。 它似乎也适用于非函数:

x <- rnorm(1e5)

list_of_x <-
  list(x, x)

pryr::object_size(x)
#> 800 kB

pryr::object_size(list_of_x)
#> 800 kB

list_of_x_copy <- unserialize(serialize(list_of_x, version = 3, connection = NULL))

pryr::object_size(list_of_x_copy)
#> 1.6 MB

我写了一组变通函数pickleR (仍然非常基本)。 结合 #197 获取 R6 实例的状态,并在它横穿对象链时跟踪对象内存地址,并恢复它们维护引用。

list_of_x_pickle <-
  pickleR::unpickle(pickleR::pickle(list_of_x, connection = NULL))

pryr::object_size(list_of_x_pickle)
#> 800 kB

x <- lapply(1:1000, function(i) {
  function() i
})

pryr::object_size(x)
#> 367 kB

x_copy <- unserialize(serialize(x, version = 3, connection = NULL))
pryr::object_size(x_copy)
#> 2.08 MB

x_pickle <-
  pickleR::unpickle(pickleR::pickle(x, connection = NULL))

pryr::object_size(x_pickle)
#> 232 kB
# I believe the size is smaller because the pickle functions
# on take the immediate enclosing environment of the function
# and reconstitute is with an emptyenv() as its parent.

也适用于 R6 类(我之前的 R6 示例的较小版本):

pryr::object_size(R6_x)
#> 1.28 MB

R6_x_copy <-
  unserialize(serialize(R6_x, connection = NULL))

pryr::object_size(R6_x_copy)
#> 17.8 MB

R6_x_pickle <-
    pickleR::unpickle(pickleR::pickle(R6_x, connection = NULL))

pryr::object_size(R6_x_pickle)
#> 1.28 MB

“腌制”嵌套类至少要慢几个数量级,但这对我来说并不是关键的阻碍因素

@d-sharpe 我快速浏览了您的pickleR包。 我认为您可以编写自己的序列化/反序列化函数来处理任何对象,而不仅仅是使用get_stateset_state函数自定义的 R6 对象。

lobstr 包的大小计算代码是用 C++ 实现的,可能会提供一些有用的指导: https :

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

wch picture wch  ·  8评论

rappster picture rappster  ·  11评论

mb706 picture mb706  ·  4评论

paulstaab picture paulstaab  ·  3评论

mattwarkentin picture mattwarkentin  ·  7评论