R中的缓存/记忆/散列选项

时间:2011-08-31 19:41:26

标签: r caching hash memoization memoise

我试图找到一种简单的方法来使用像Perl在R中的散列函数(基本上是缓存),因为我打算进行Perl风格的散列并编写我自己的计算备忘录。然而,其他人已经打败了我,并有包装备忘。我挖的越多,我发现的越多,例如memoiseR.cache,但差异并不容易明确。另外,目前还不清楚如何使用Perl风格的哈希(或Python风格的词典)并编写一个自己的memoization,而不是使用hash包,这似乎不支持两个memoization包。

由于我无法找到有关CRAN或其他地方的信息来区分选项,或许这应该是关于SO的社区维基问题:R中的记忆和缓存有哪些选项,它们的区别是什么?


作为比较的基础,这里是我找到的选项列表。此外,在我看来,所有都依赖于散列,所以我也会注意到散列选项。密钥/值存储在某种程度上是相关的,但是会打开关于数据库系统的大量蠕虫(例如BerkeleyDB,Redis,MemcacheDB和scores of others)。

看起来选项如下:

散列

  • digest - 为任意R对象提供散列。

记忆化

  • memoise - 一个非常简单的函数记忆工具。
  • R.cache - 为memoization提供了更多功能,但似乎有些功能缺乏示例。

缓存

  • hash - 提供类似于Perl的哈希和Python词典的缓存功能。

键/值存储

这些是R对象外部存储的基本选项。

点检查

其他

  • Base R支持:命名向量和列表,数据框的行和列名称,以及环境中项目的名称。在我看来,使用列表有点像kludge。 (还有pairlist,但it is deprecated。)
  • data.table包支持快速查找数据表中的元素。

用例

虽然我最感兴趣的是了解选项,但我有两个基本用例:

  1. 缓存:简单计算字符串。 [注意:这不适用于NLP,但一般用途,因此NLP库是过度的;表格不合适,因为我不想等到整个字符串集加载到内存中。 Perl风格的哈希处于适当的实用水平。]
  2. 怪异计算的记忆。
  3. 这些真的出现了,因为我digging in to the profiling of some slooooow code而且我真的只想计算简单的字符串,看看我是否可以通过记忆加速计算。能够散列输入值,即使我没有记忆,也会让我看看memoization是否有帮助。


    注1:CRAN Task View on Reproducible Research列出了几个软件包(cacherR.cache),但没有详细说明使用方法。

    注2:为了帮助其他人查找相关代码,这里有一些关于某些作者或包的注释。一些作者使用SO。 :)

    • Dirk Eddelbuettel:digest - 很多其他套餐依赖于此。
    • Roger Peng:cacherfilehashstashR - 它们以不同的方式解决不同的问题;有关更多软件包,请参阅Roger's site
    • Christopher Brown:hash - 似乎是一个有用的软件包,但不幸的是,ODG的链接已经关闭。
    • Henrik Bengtsson:R.cache& Hadley Wickham:memoise - 现在还不清楚何时更喜欢一个包装而不是另一个包装。

    注3:有些人使用memoise / memoisation,其他人使用memoize / memoization。如果您正在寻找,请注意。 Henrik使用“z”而Hadley使用“s”。

3 个答案:

答案 0 :(得分:9)

对于字符串的简单计数(并且不使用table或类似的),multiset数据结构似乎非常合适。 environment对象可用于模拟此。

# Define the insert function for a multiset
msetInsert <- function(mset, s) {
    if (exists(s, mset, inherits=FALSE)) {
        mset[[s]] <- mset[[s]] + 1L
    } else {
        mset[[s]] <- 1L 
    }
}

# First we generate a bunch of strings
n <- 1e5L  # Total number of strings
nus <- 1e3L  # Number of unique strings
ustrs <- paste("Str", seq_len(nus))

set.seed(42)
strs <- sample(ustrs, n, replace=TRUE)


# Now we use an environment as our multiset    
mset <- new.env(TRUE, emptyenv()) # Ensure hashing is enabled

# ...and insert the strings one by one...
for (s in strs) {
    msetInsert(mset, s)
}

# Now we should have nus unique strings in the multiset    
identical(nus, length(mset))

# And the names should be correct
identical(sort(ustrs), sort(names(as.list(mset))))

# ...And an example of getting the count for a specific string
mset[["Str 3"]] # "Str 3" instance count (97)

答案 1 :(得分:9)

我对memoise没有好运,因为它给我试过的打包的某些功能带来了too deep recursive问题。 R.cache我运气好了。以下是我从R.cache文档改编的更加注释的代码。代码显示了执行缓存的不同选项。

# Workaround to avoid question when loading R.cache library
dir.create(path="~/.Rcache", showWarnings=F) 
library("R.cache")
setCacheRootPath(path="./.Rcache") # Create .Rcache at current working dir
# In case we need the cache path, but not used in this example.
cache.root = getCacheRootPath() 
simulate <- function(mean, sd) {
    # 1. Try to load cached data, if already generated
    key <- list(mean, sd)
    data <- loadCache(key)
    if (!is.null(data)) {
        cat("Loaded cached data\n")
        return(data);
    }
    # 2. If not available, generate it.
    cat("Generating data from scratch...")
    data <- rnorm(1000, mean=mean, sd=sd)
    Sys.sleep(1) # Emulate slow algorithm
    cat("ok\n")
    saveCache(data, key=key, comment="simulate()")
    data;
}
data <- simulate(2.3, 3.0)
data <- simulate(2.3, 3.5)
a = 2.3
b = 3.0
data <- simulate(a, b) # Will load cached data, params are checked by value
# Clean up
file.remove(findCache(key=list(2.3,3.0)))
file.remove(findCache(key=list(2.3,3.5)))

simulate2 <- function(mean, sd) {
    data <- rnorm(1000, mean=mean, sd=sd)
    Sys.sleep(1) # Emulate slow algorithm
    cat("Done generating data from scratch\n")
    data;
}
# Easy step to memoize a function
# aslo possible to resassign function name.
This would work with any functions from external packages. 
mzs <- addMemoization(simulate2)

data <- mzs(2.3, 3.0)
data <- mzs(2.3, 3.5)
data <- mzs(2.3, 3.0) # Will load cached data
# aslo possible to resassign function name.
# but different memoizations of the same 
# function will return the same cache result
# if input params are the same
simulate2 <- addMemoization(simulate2)
data <- simulate2(2.3, 3.0)

# If the expression being evaluated depends on
# "input" objects, then these must be be specified
# explicitly as "key" objects.
for (ii in 1:2) {
    for (kk in 1:3) {
        cat(sprintf("Iteration #%d:\n", kk))
        res <- evalWithMemoization({
            cat("Evaluating expression...")
            a <- kk
            Sys.sleep(1)
            cat("done\n")
            a
        }, key=list(kk=kk))
        # expressions inside 'res' are skipped on the repeated run
        print(res)
        # Sanity checks
        stopifnot(a == kk)
        # Clean up
        rm(a)
    } # for (kk ...)
} # for (ii ...)

答案 2 :(得分:1)

@biocyperman solution相关。 R.cache有一个包装函数,用于避免加载,保存和评估缓存。请参阅修改后的函数:

R.cache提供了一个用于加载,评估和保存的包装器。您可以像这样简化代码:

simulate <- function(mean, sd) {
key <- list(mean, sd)
data <- evalWithMemoization(key = key, expr = {
    cat("Generating data from scratch...")
    data <- rnorm(1000, mean=mean, sd=sd)
    Sys.sleep(1) # Emulate slow algorithm
    cat("ok\n")
    data})
}