3.1 R语言本地缓存memoise
问题
如何提高R程序性能?
引言
缓存技术在计算机系统中运用得非常广泛,对于高并发访问的应用来说,缓存技术是性价比最高的性能提升方案,特别是对于重复性计算,缓存能为我们节省大量的CPU时间,可能达到99%。R语言以统计计算著名,但其中很多算法包都是在进行大量重复的计算。优化正在进行,改变已经开始。以Hadley Wickham为代表的R语言领军人物,正在让R快起来!
3.1.1 memoise介绍
memoise是一个非常简单的缓存包,以本地缓存为基础,减少单机的重复性计算,从而间接地提升了单机的CPU性能。当你用相同的参数对同一个函数执行第二次计算的时候,如果你可以直接用第一次计算过的结果作为第二次计算的结果,而不是再重算一遍,那么我们就节省了CPU时间。
memoise包的API非常简单,只有2个函数。
- memoize: 定义缓存函数,把一个函数计算的结果加载到本地缓存。
- forget: 重置缓存函数,把一个函数的计算结果从本地缓存中删除。
3.1.2 memoise安装
本节使用的系统环境是:
- Win7 64bit
- R: 3.0.1 x86_64-w64-mingw32/x64 b4bit
注:memoise安装同时支持Win7环境和Linux环境。
memoise安装
~ R # 启动R程序
> install.packages("memoise") # 安装memoise包
> library(memoise) # memoise加载
3.1.3 memoise使用
下面我们进行缓存实验,假设一个函数运行时CPU耗时1秒。
> fun <- memoise(function(x) { Sys.sleep(1); runif(1) })# 定义缓存函数,运行时停1秒
> system.time(print(fun())) # 第一次执行fun函数,等待1秒完成
[1] 0.05983416
用户 系统 流逝
0 0 1
> system.time(print(fun())) # 第二次执行fun函数,从缓存中获得结果,瞬时完成
[1] 0.05983416
用户 系统 流逝
0 0 0
> forget(fun) # 重置缓存函数,清空函数的缓存数据
[1] TRUE
> system.time(print(fun())) # 第三次执行fun函数,等待1秒完成
[1] 0.6001663
用户 系统 流逝
0 0 1
> system.time(print(fun())) # 第四次执行fun函数,从缓存中获得结果,瞬时完成
[1] 0.6001663
用户 系统 流逝
0 0 0
对上面执行过程的描述:
- 通过memoise函数,创建缓存函数fun。
- 第一次执行fun函数, 等待1秒。
- 第二次执行fun函数, 无等待,直接从缓存中获得结果。
- 通过forget函数,清空缓存中fun函数的结果。
- 第三次执行fun函数, 由于fun函数的结果被清空,所以重新执行fun函数,等待1秒。
- 第四次执行fun函数, 无等待,直接从缓存中返回结果。
3.1.4 memoise源代码分析
memoise项目是一个典型的以面向对象编程方式实现的R语言项目,R语言的面向对象编程在本书姊妹篇《R的极客理想——高级开发篇》一书中给大家详细介绍。memoise项目的源代码在Gihub上可以找到,项目地址是 https://github.com/hadley/memoise。
1. memoise函数源代码
接下来,我们解读memoise函数的源代码,memoise函数主要做了3件事。
- new_cache()函数,创建新的缓存空间,给f函数
- 生成f函数的hash值,作为key
- 返回缓存后的,f函数引用
memoise <- memoize <- function(f) { # 定义同名函数memoise和memoize
cache <- new_cache() # 创建新的缓存空间
memo_f <- function(...) { # 生成hash值
hash <- digest(list(...))
if (cache$has_key(hash)) {
cache$get(hash)
} else {
res <- f(...)
cache$set(hash, res)
res
}
}
attr(memo_f, "memoised") <- TRUE
return(memo_f)
}
2. forget函数源代码
forget函数主要做了2件事。
- 检查环境中,是否缓存了f()函数
- 如果有f()函数的缓存,则清空f()函数的缓存值
forget <- function(f) {
if (!is.function(f)) return(FALSE) # 判断函数是否在缓存中
env <- environment(f)
if (!exists("cache", env, inherits = FALSE)) return(FALSE)
cache <- get("cache", env) # 清空函数的缓存值
cache$reset()
TRUE
}
3. 私有函数:new_cache函数源代码
new_cache函数主要做了3件事。
- 在new_cache()函数里,定义cache对象,保存在env的环境中。
- 通过new_cache()函数,构造list类型对象,包括reset, set, get, has_key, keys方法。
- 通过list对象,对cache对象进行访问操作。
new_cache <- function() {
cache <- NULL # 定义cache对象
cache_reset <- function() { # 定义清空缓存的函数
cache <<- new.env(TRUE, emptyenv())
}
cache_set <- function(key, value) { # 定义设置缓存的函数
assign(key, value, env = cache)
}
cache_get <- function(key) { # 定义取得缓存的函数
get(key, env = cache, inherits = FALSE)
}
cache_has_key <- function(key) { # 检查缓存是否存在
exists(key, env = cache, inherits = FALSE)
}
cache_reset() # 运行清空缓存
list( # 返回缓存对象
reset = cache_reset,
set = cache_set,
get = cache_get,
has_key = cache_has_key,
keys = function() ls(cache)
)
}
从源代码中,我们不仅能发现memoise包的设计思想,还能感受到作者对R语言深刻的理解。memoise包的代码简洁、高效,值得大家用心学习。