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

对上面执行过程的描述:

  1. 通过memoise函数,创建缓存函数fun。
  2. 第一次执行fun函数, 等待1秒。
  3. 第二次执行fun函数, 无等待,直接从缓存中获得结果。
  4. 通过forget函数,清空缓存中fun函数的结果。
  5. 第三次执行fun函数, 由于fun函数的结果被清空,所以重新执行fun函数,等待1秒。
  6. 第四次执行fun函数, 无等待,直接从缓存中返回结果。

3.1.4 memoise源代码分析

memoise项目是一个典型的以面向对象编程方式实现的R语言项目,R语言的面向对象编程在本书姊妹篇《R的极客理想——高级开发篇》一书中给大家详细介绍。memoise项目的源代码在Gihub上可以找到,项目地址是 https://github.com/hadley/memoise

1. memoise函数源代码

接下来,我们解读memoise函数的源代码,memoise函数主要做了3件事。

  1. new_cache()函数,创建新的缓存空间,给f函数
  2. 生成f函数的hash值,作为key
  3. 返回缓存后的,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件事。

  1. 检查环境中,是否缓存了f()函数
  2. 如果有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件事。

  1. 在new_cache()函数里,定义cache对象,保存在env的环境中。
  2. 通过new_cache()函数,构造list类型对象,包括reset, set, get, has_key, keys方法。
  3. 通过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包的代码简洁、高效,值得大家用心学习。

results matching ""

    No results matching ""