你好。
抄送@leobelen
我们正在开发一个包Rpolyhedra 。 Rpolyhedra 是从互联网公共可用资源中抓取的多面体数据库。
它从抓取的来源制作多面体 R6 对象。 抓取过程的分类帐和数据库本身是 R6 对象。
然后我们将所有内容都放在一个 RDS 文件中,用于加速、回归测试、设置数据库的预抓取完整版本以及其他功能。
@wch要求我们不要在 RDS 文件中包含 R6 对象,因为最终用户计算机中安装的不同 R6 版本之间不兼容。
我们认为解决方案是在将 R6 对象保存到 RDS 时对其进行序列化。 因此,具有不同 R6 版本的用户可以访问相同的 RDS,而不会出现不兼容的风险。
我阅读了 R6 代码,评估如何应用元编程来访问对象的字段(状态)对我来说并不简单,更不用说找出如何保证序列化/反序列化是安全的。
我们开始用我们自己的序列化方法来做它,但想知道是否有共识,这个功能的路线图将由包本身解决。
我看到有一个方法as.list.R6
#91 并且在内部使用了get_nonfunctions
可能很有用。 但不确定我们是否能够提出一个奇特且社区满意的解决方案。 如果对提议的功能的价值存在共识,那么这个问题应该由精通 R6 哲学的开发人员解决。
R6 特别注意使存储的 R6 对象即使在具有不同版本的 R6 的计算机上也能正常运行。 但是,不能保证对象本身在不同 R6 版本的结构上完全相同。
Rpolyhedra (qbotics/Rpolyhedra#21) 中的问题是您将存储的 R6 对象与动态生成的对象进行比较。 存储的对象可能是在具有不同 R6 版本的不同机器上创建的,因此结果对象可能与动态对象不同,即使所有输入都相同。 在那个问题中,R6 中的clone()
方法发生了变化,这就是导致比较失败的原因。
我认为您需要具体说明您对该序列化的期望。 serialize()
函数将序列化对象(它是saveRDS()
使用的),但由于我上面描述的原因,它不适合您的目的。
@wch我一直在研究基于 R6 的包,这要求我在某些点保存对象的“状态”。 一些对象与其他对象中的 R6 对象列表完全嵌套在一起。 创建时,内存中的大小以 10 兆 MB 为单位。 当保存(通过save
、 saveRDS
或serialize
)然后重新加载时,内存需求会爆炸到> 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
我认为大小增加可能是因为serialize
和unserialize
不够聪明,无法对相同函数的组件(即主体和形式)进行重复数据删除。 例如:
这是一个简单的例子(没有 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_state
和set_state
函数自定义的 R6 对象。
lobstr 包的大小计算代码是用 C++ 实现的,可能会提供一些有用的指导: https :