【问题标题】:The best way to construct a function with memory用内存构造函数的最佳方法
【发布时间】:2011-03-13 06:07:13
【问题描述】:

早安,

我有一些非常缓慢和复杂的功能,比如f[x,y]。我需要构造它的详细ContourPlot。此外,函数f[x,y] 有时会由于缺少物理内存而失败。在这种情况下,我必须自己停止评估并调查点 {x,y} 的问题案例。然后我应该可以将元素 {x,y,f[x,y]} 添加到f[x,y] 的计算值列表中(比如“缓存”)并重新开始评估ContourPlotContourPlot 必须从缓存中获取所有已计算的 f 值。我希望将此类列表存储在某个文件中,以便以后能够重用它。手动将问题点添加到此文件可能更简单。

如果f 的计算值列表可能包含 10000-50000 个点,那么实现此功能的最快方法是什么?

【问题讨论】:

  • 如果您必须创建{x,y,f[x,y]} 的列表,使用ListContourPlot[pts] 而不是ContourPlot 可能是值得的。
  • @Brett ListContourPlot 将不会继续计算 f 的附加值以使绘图更好。我的需要是有可能从f[x,y] 失败的某个问题点继续计算ContourPlot

标签: wolfram-mathematica


【解决方案1】:

假设我们的慢速函数有签名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"];
]

flogfmem 相同,只是它还在运行日志中写入了一个条目,可用于在以后的会话中恢复缓存的定义。最后一个表达式在找到现有日志文件时重新加载这些定义(如果文件不存在则创建该文件)。

当需要手动干预时,日志文件的文本性质很方便。请注意,浮点数的文本表示会引入不可避免的舍入错误,因此在从日志文件重新加载值后,您可能会得到略有不同的结果。如果这很重要,您可以考虑使用二进制 DumpSave 功能,尽管我会将这种方法的细节留给读者,因为它不太方便保存增量日志。

SQL 方法

如果内存真的很紧,并且您希望避免使用大型内存缓存来为其他计算腾出空间,那么之前的策略可能不合适。在这种情况下,您可以考虑使用 Mathematica 的内置 SQL 数据库将缓存完全存储在外部:

fsql[x_, y_] :=
  loadCachedValue[x, y] /. $Failed :> saveCachedValue[x, y, f[x, y]]

我在下面定义loadCachedValuesaveCachedValue。基本思想是创建一个 SQL 表,其中每行包含一个xyf 三元组。每次需要值时都会查询 SQL 表。请注意,这种方法大大比内存中的缓存慢,所以当f 的计算花费的时间比 SQL 访问时间长得多时,它最有意义。 SQL 方法不会遭受影响文本日志文件方法的舍入错误。

下面是 loadCachedValuesaveCachedValue 的定义,以及其他一些有用的辅助函数:

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 数据库。 replaceCachedValueclearCacheshowCache 用于手动干预。

【讨论】:

  • 我非常喜欢阅读您的回复。非常感谢你! “文件支持的内存”解决方案非常优雅和快速。这正是我需要的。 SQL 方法非常有趣,也是一种很好的储备策略。
  • +1,我同意@Timo。从来没有想过用 SQL 表来备份它。现在我只需要找到一些东西来应用它......
  • 对如何维护每行只有一个表达式的运行日志的出色答案的有用补充:PutAppend with PageWidth -> Infinity
  • 如果我的函数在一个连续变量上,当我查询非常接近我存储的点时,我可以让数学从内存中插值(线性说)吗?
  • @ThomasAhle 我们可以通过将保存的输入和输出值列表传递给Interpolation 来创建一个插值函数。恢复这些列表的方法需要在我们选择的用于记住值的任何策略中添加某种查询函数。
【解决方案2】:

执行此操作的最简单且可能最有效的方法就是将缓存值设置为函数的特殊情况定义。由于散列,查找速度相当快。

一个函数:

In[1]:= f[x_, y_] := Cos[x] + Cos[y]

在等高线图中使用了哪些点?

In[2]:= points = Last[
   Last[Reap[
     ContourPlot[f[x, y], {x, 0, 4 Pi}, {y, 0, 4 Pi}, 
      EvaluationMonitor :> Sow[{x, y}]]]]];

In[3]:= Length[points]

Out[3]= 10417

使用预先计算的 10000 个评估值设置 f 的版本:

In[4]:= Do[With[{x = First[p], y = Last[p]}, precomputedf[x, y] = f[x, y];], {p, 
   Take[points, 10000]}];

在上面,您可以使用 precomputedf[x, y] = z 之类的东西而不是 precomputed[x, y] = f[x, y],其中 z 是您存储在外部文件中的预计算值。

这是只评估 f 的“else”案例:

In[5]:= precomputedf[x_, y_] := f[x, y]

比较时间:

In[6]:= ContourPlot[f[x, y], {x, 0, 4 Pi}, {y, 0, 4 Pi}]; // Timing

Out[6]= {0.453539, Null}

In[7]:= ContourPlot[precomputedf[x, y], {x, 0, 4 Pi}, {y, 0, 4 Pi}]; // Timing

Out[7]= {0.440996, Null}

时间差异不大,因为在这个例子中 f 不是一个昂贵的函数。

针对您的特定应用程序的单独说明:也许您可以改用ListContourPlot。然后,您可以准确选择要评估的点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-19
    • 2015-04-12
    相关资源
    最近更新 更多