Ответ 1
Предположим, что наша медленная функция имеет подпись f[x, y]
.
Чистый подход в памяти
Если вас устраивает кеш в памяти, проще всего использовать memoization:
[email protected]
fmem[x_, y_] := fmem[x, y] = f[x, y]
Это добавляет определение к себе каждый раз, когда он вызывается с комбинацией аргументов, которые он не видел раньше.
Подход, основанный на файлах в памяти
Однако, если у вас заканчивается память или возникают сбои ядра во время длинных вычислений, вы захотите вернуть этот кеш с некоторой настойчивостью. Самое простое - сохранить исполняемый файл журнала:
$runningLogFile = "/some/directory/runningLog.txt";
[email protected]
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
Если память действительно жесткая, и вы хотите избежать большого кэша в памяти, чтобы освободить место для других вычислений, предыдущая стратегия может оказаться неприемлемой. В этом случае вы можете использовать встроенную базу данных 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
предоставляются для ручных вмешательств.