Давайте предположим, что наша медленная функция имеет сигнатуру f[x, y]
.
Чистый подход в памяти
Если вас устраивает кэш в памяти,Самое простое, что можно сделать, это использовать памятку:
Clear@fmem
fmem[x_, y_] := fmem[x, y] = f[x, y]
Это добавляет к себе определение каждый раз, когда оно вызывается с комбинацией аргументов, которых раньше не было.
Подход с поддержкой файловой памяти в памяти
Однако, если у вас заканчивается память или возникают сбои ядра во время длительных вычислений, вам может понадобиться резервное копирование этого кэша с некоторой настойчивостью.Самое простое - сохранить текущий файл журнала:
$runningLogFile = "/some/directory/runningLog.txt";
Clear@flog
flog[x_, y_] := flog[x, y] = f[x, y] /.
v_ :> (PutAppend[Unevaluated[flog[x, y] = v;], $runningLogFile]; v)
If[FileExistsQ[$runningLogFile]
, Get[$runningLogFile]
, Export[$runningLogFile, "", "Text"];
]
flog
совпадает с fmem
, за исключением того, что он также записывает запись в текущий журнал, который можно использовать для восстановлениякэшированное определение в более поздней сессии.Последнее выражение перезагружает эти определения, когда находит существующий файл журнала (или создает файл, если он не существует).
Текстовая природа файла журнала удобна, когда требуется ручное вмешательство.Имейте в виду, что текстовое представление чисел с плавающей точкой приводит к неизбежным ошибкам округления, поэтому вы можете получить немного другие результаты после перезагрузки значений из файла журнала.Если это вызывает серьезную обеспокоенность, вы можете рассмотреть возможность использования двоичной функции DumpSave
, хотя я оставлю подробности этого подхода читателю, поскольку он не так удобен для ведения инкрементного журнала.
Подход SQL
Если памяти действительно мало, и вы хотите избежать большого кеша в памяти, чтобы освободить место для других вычислений, предыдущая стратегия может быть неуместной.В этом случае вы можете использовать встроенную базу данных Mathematica для хранения кеша полностью внешне:
fsql[x_, y_] :=
loadCachedValue[x, y] /. $Failed :> saveCachedValue[x, y, f[x, y]]
Я определяю loadCachedValue
и saveCachedValue
ниже.Основная идея заключается в создании таблицы SQL, в которой каждая строка содержит тройку x
, y
, f
.Таблица SQL запрашивается каждый раз, когда требуется значение.Обратите внимание, что этот подход на существенно медленнее, чем кэш в памяти, поэтому он наиболее целесообразен, когда вычисление f
занимает намного больше времени, чем время доступа SQL.Подход SQL не страдает от ошибок округления, которые мешали подходу текстового файла журнала.
Теперь следуют определения loadCachedValue
и saveCachedValue
, наряду с некоторыми другими полезными вспомогательными функциями:
Needs["DatabaseLink`"]
$cacheFile = "/some/directory/cache.hsqldb";
openCacheConnection[] :=
$cache = OpenSQLConnection[JDBC["HSQL(Standalone)", $cacheFile]]
closeCacheConnection[] :=
CloseSQLConnection[$cache]
createCache[] :=
SQLExecute[$cache,
"CREATE TABLE cached_values (x float, y float, f float)
ALTER TABLE cached_values ADD CONSTRAINT pk_cached_values PRIMARY KEY (x, y)"
]
saveCachedValue[x_, y_, value_] :=
( SQLExecute[$cache,
"INSERT INTO cached_values (x, y, f) VALUES (?, ?, ?)", {x, y, value}
]
; value
)
loadCachedValue[x_, y_] :=
SQLExecute[$cache,
"SELECT f FROM cached_values WHERE x = ? AND y = ?", {x, y}
] /. {{{v_}} :> v, {} :> $Failed}
replaceCachedValue[x_, y_, value_] :=
SQLExecute[$cache,
"UPDATE cached_values SET f = ? WHERE x = ? AND y = ?", {value, x, y}
]
clearCache[] :=
SQLExecute[$cache,
"DELETE FROM cached_values"
]
showCache[minX_, maxX_, minY_, maxY_] :=
SQLExecute[$cache,
"SELECT *
FROM cached_values
WHERE x BETWEEN ? AND ?
AND y BETWEEN ? AND ?
ORDER BY x, y"
, {minX, maxX, minY, maxY}
, "ShowColumnHeadings" -> True
] // TableForm
Этот код SQL использует значения с плавающей запятой в качестве первичных ключей.Обычно это сомнительная практика в SQL, но она отлично работает в данном контексте.
Вы должны вызвать openCacheConnection[]
, прежде чем пытаться использовать любую из этих функций.Вам следует позвонить closeCacheConnection[]
после того, как вы закончите.Только один раз, вы должны вызвать createCache[]
для инициализации базы данных SQL.replaceCachedValue
, clearCache
и showCache
предназначены для ручного вмешательства.