knitr 中更强大的缓存功能

knitr 中更强大的缓存功能

💡 原文英文,约800词,阅读约需3分钟。
📝

内容提要

knitr 缓存功能改进:新增 process_cache() 泛型,使 terra 栅格等指针对象可安全缓存;缓存改用 xfun 的 .rds 格式,兼容旧缓存;dependson 支持依赖未缓存代码块,通过将上游代码纳入缓存键实现失效,autodep 同样适用。

🔎

延伸解读

指针对象缓存的新机制

对于 terra 栅格等由 C/C++ 管理内存的指针对象,直接缓存会导致悬空指针,引发段错误或返回垃圾数据。新增的 process_cache() 泛型在写入缓存前(pack=TRUE)和读取后(pack=FALSE)调用,允许包作者注册方法将对象转换为可序列化形式。例如 terra 使用 wrap() 和 unwrap() 实现安全缓存,且包作者可在 .onLoad() 中注册,用户无需额外操作。

缓存存储格式的兼容性升级

缓存存储从非公开 API tools:::makeLazyLoadDB() 改为 xfun::lazy_save() 和 xfun::lazy_load(),生成普通的 .rds 文件。旧格式的 .rdb/.rdx 缓存仍可读取,不会破坏现有工作流;新缓存文件更简单,便于管理和迁移。这一改动降低了内部依赖,提升了长期可维护性。

依赖未缓存代码块的失效逻辑

dependson 现在支持依赖未缓存的代码块。过去上游必须缓存才能失效下游,否则警告且无效。新方法将依赖链上所有代码块的代码纳入缓存键,因此编辑未缓存的上游代码会改变键值,自动使下游缓存失效。示例中 prep 未缓存,但 model 缓存会因 prep 变化而重新运行。autodep=TRUE 同样适用。

Q&A

knitr 缓存中 process_cache() 泛型的作用是什么?

process_cache() 是一个新的 S3 泛型,knitr 在将对象写入缓存前(pack = TRUE)和从缓存读取后(pack = FALSE)调用它。通过为特定类注册方法,可以将指针对象转换为可序列化的形式,并在读取时恢复,从而安全地缓存如 terra 栅格等对象。

如何让 terra 栅格对象在 knitr 中可缓存?

为 SpatRaster 和 PackedSpatRaster 类注册 process_cache() 方法:对于 SpatRaster,pack = TRUE 时调用 terra::wrap(),pack = FALSE 时返回原对象;对于 PackedSpatRaster,pack = TRUE 时返回原对象,pack = FALSE 时调用 terra::unwrap()。包作者可以在自己的 .onLoad() 中注册这些方法,用户无需额外操作。

knitr 缓存存储格式有什么变化?旧缓存还能用吗?

缓存存储格式改为使用 xfun::lazy_save() 和 xfun::lazy_load()(纯 .rds 文件),替代了非公开 API tools:::makeLazyLoadDB()。旧格式(.rdb/.rdx)的缓存仍然可以读取,因此不会破坏现有缓存;新缓存是更简单的文件。

dependson 现在支持依赖未缓存的代码块了吗?如何工作?

是的,现在支持。以前 dependson 要求上游代码块也必须被缓存,否则会警告且依赖无效。现在,被缓存代码块所依赖的所有代码块(包括传递依赖)的代码会被纳入缓存键中。因此,即使上游代码块未缓存,修改其代码也会改变缓存键,从而使下游缓存失效。

autodep = TRUE 是否也能利用新的依赖失效机制?

是的,autodep = TRUE 同样适用。当 autodep = TRUE 时,knitr 会自动推断依赖关系,并且新的缓存键机制会将这些依赖的代码纳入考虑,从而在依赖变化时正确使缓存失效。

为什么以前不能缓存 terra 栅格这类对象?

因为 terra 栅格等对象是指向由 C 或 C++ 管理的内存的指针。使用默认机制保存后再加载,会得到悬空指针,导致段错误或返回垃圾数据。因此过去建议不要缓存这些代码块。

🏷️

标签

➡️

继续阅读