R: кэширование/память для сред

nЯ хотел бы использовать memoization для кэширования результатов некоторых дорогостоящих операций, чтобы они не вычислялись и снова.

Как memoise, так и R.cache подходит для моих нужд. Однако я обнаружил, что кэширование не является надежным для вызовов.

Вот пример, демонстрирующий проблему, которую я вижу:

library(memoise)

# Memoisation works: b() is called only once
a <- function(x) runif(1)
replicate(5, a())
b <- memoise(a)
replicate(5, b())

# Memoisation fails: mfn() is called every single time
ProtoTester <- proto(
  calc = function(.) {
    fn <- function() print(runif(1))
    mfn <- memoise(fn)
    invisible(mfn())
  }      
)
replicate(5, ProtoTester$calc())

Обновлено на основе ответа

На этот вопрос могут быть разные ответы в зависимости от того, используется ли постоянное или непостоянное кэширование. Для непостоянного кэширования (например, memoise) может потребоваться однократное назначение, и тогда приведенный ниже ответ является хорошим способом. Постоянное кэширование (такое как R.cache) работает между сеансами и должно быть устойчивым к нескольким назначениям. Описанный выше подход работает с R.cache. Несмотря на несколько назначений, fn вызывается только один раз с R.cache. Он будет вызываться дважды с memoise.

> ProtoTester <- proto(
+     calc = function(.) {
+         fn <- function() print(runif(1))
+         invisible(memoizedCall(fn))
+     }      
+ )
> replicate(5, ProtoTester$calc())
[1] 0.977563
[1] 0.1279641
[1] 0.01358866
[1] 0.9993092
[1] 0.3114813
[1] 0.97756303 0.12796408 0.01358866 0.99930922 0.31148128
> ProtoTester <- proto(
+     calc = function(.) {
+         fn <- function() print(runif(1))
+         invisible(memoizedCall(fn))
+     }      
+ )
> replicate(5, ProtoTester$calc())
[1] 0.97756303 0.12796408 0.01358866 0.99930922 0.31148128

Причина, по которой я думал, что у меня проблема с R.cache, заключается в том, что я передал метод proto в качестве функции memoizedCall. Методы proto привязаны к средам таким образом, что R.cache с ними трудно. Что вам нужно сделать в этом случае, так это отменить привязку функции (перейти от созданного метода к простой функции), а затем передать объект вручную в качестве первого аргумента. В следующем примере показано, как это работает (и Report, и Report$loader являются объектами proto:

# This will not memoize the call
memoizedCall(Report$loader$download_report)

# This works as intended
memoizedCall(with(Report$loader, download_report), Report$loader)

Я хотел бы знать, почему R.cache работает с обычными функциями, привязанными к средам, но не работает с proto экземплярами методов.


person Sim    schedule 06.07.2012    source источник


Ответы (2)


В вашем коде функция запоминается заново при каждом вызове. Следующее должно работать: он запоминается только один раз, когда он определен.

ProtoTester <- proto(
  calc = {
    fn <- function() print(runif(1))
    mfn <- memoise(fn)
    function(.) mfn()
  }
)
replicate(5, ProtoTester$calc())
person Vincent Zoonekynd    schedule 06.07.2012
comment
Я неправильно понял механизм идентификации объекта, который используют реализации memoise. Поскольку R.cache имеет постоянство, связанное с мемоизацией, я думал, что он предназначен для работы на основе содержимого/кода функции, а не на ее внутреннем идентификаторе R, иначе он не будет работать между сеансами. Создание выражения с мемоизацией в качестве побочного эффекта — хороший шаблон. Ваш код отлично работает с R.cache даже между назначениями (выполнение задания ProtoTester несколько раз, которое назначает fn несколько раз). Интересно, почему у меня раньше не работало... - person Sim; 06.07.2012
comment
Я проверил еще раз и обнаружил, что R.cache работает с моим старым кодом. Я, должно быть, упустил из виду что-то простое. - person Sim; 06.07.2012
comment
Я обновил вопрос и уточнил различия в поведении, когда речь идет о настойчивости. - person Sim; 06.07.2012
comment
Я отследил источник проблемы с R.cache и proto. R.cache кажется сбитым с толку методами создания экземпляров proto. Отвязка их и передача объекта явно решает проблему. - person Sim; 06.07.2012

Альтернативным решением может быть использование evals для оценки из (моего) пакета pander с внутренний (временный в среде текущего сеанса R или постоянный с дисковым хранилищем) механизм кэширования. Краткий пример на основе вашего кода:

library(pander)
ProtoTester <- proto(
  calc = function(.) {
    fn <- function() runif(1)
    mfn <- evals('fn()')[[1]]$result
    invisible(mfn)
  }      
)

И запуск evals с выключенным и включенным кешем приведет к:

> evals.option('cache', FALSE)
> replicate(5, ProtoTester$calc())
[1] 0.7152186 0.4529955 0.4160411 0.1166872 0.8776698

> evals.option('cache', TRUE)
> evals.option('cache.time', 0)
> replicate(5, ProtoTester$calc())
[1] 0.7716874 0.7716874 0.7716874 0.7716874 0.7716874

Обратите внимание, что evals.option функция si скоро будет переименована в evalsOption, чтобы смягчить R CMD check предупреждения о методах S3.

person daroczig    schedule 06.07.2012