19.5 记忆化

记忆化(Memoization)是一种将慢速函数调用的结果缓存起来,当再次以相同输入调用函数时直接返回缓存值而无需重新计算的技术。如果同样的输入以已知且可预测的方式反复出现,用查找表替代函数调用是非常常见的做法。记忆化本质上就是这种实践的扩展——即使在运行过程中,也会为之前未见过的参数动态扩展查找表。关于记忆化的基本理论基础,可参见维基百科或任何本科级别的计算机科学教科书。

Octave 的 memoize 函数为任何用户函数或 Octave 函数(包括编译函数)提供了开箱即用的记忆化功能。

 
mem_fcn_handle = memoize (fcn_handle)

为函数 fcn_handle 创建一个记忆化版本 mem_fcn_handle

每次调用记忆化版本 mem_fcn_handle 时,会将输入与内部维护的表进行比对。如果该输入组合之前出现过,则直接从表中返回函数调用的结果,而无需再次完整求值。这能显著加速那些以相同输入多次调用的函数的执行。

例如,我们有一个用户编写的慢速函数 slow_fcn,并将其记忆化到一个新的句柄 cyc。两个版本的第一次执行耗时相同,但后续执行中,记忆化版本直接返回之前计算过的值,从而将 2.4 秒的运行时间减少到仅 2.4 毫秒。最后的检查验证了两个版本返回的结果相同。

>> tic; p = slow_fcn (5040); toc
Elapsed time is 2.41244 seconds.
>> tic; p = slow_fcn (5040); toc
Elapsed time is 2.41542 seconds.

>> cyc = memoize (@slow_fcn);
>> tic; r = cyc (5040); toc
Elapsed time is 2.42609 seconds.
>> tic; r = cyc (5040); toc
Elapsed time is 0.00236511 seconds.

>> all (p == r)
ans = 1

另请参阅: clearAllMemoizedCaches.

要对函数 z = foo(x, y) 进行记忆化,可以使用以下通用模式:

foo2 = memoize (@(x, y) foo(x, y));
z = foo2 (x, y);

在上面的例子中,第一行创建了函数 foo 的记忆化版本 foo2。对于只需要简单封装的函数,这一行也可以简写为:

foo2 = memoize (@foo);

第二行 z = foo2 (x, y); 调用记忆化版本 foo2 而非原始函数,这使得 memoize 能够拦截调用——如果该输入组合之前出现过,则直接用查找表中的值替代,而无需再次计算原始函数。

注意,这并不会加速函数的首次调用,只会加速后续调用。

注意,由于 memoize 为每个函数创建和管理查找表会带来额外开销,该技术仅适用于至少需要几秒才能执行完成的函数。这类函数可以被仅需约一毫秒的表查询所替代。但如果原始函数本身只需要几毫秒,记忆化并不能加速其执行。

递归函数也可以进行记忆化,使用如下模式:

function z = foo (x, y)
  persistent foo2 = memoize (@foo);
  foo2.CacheSize = 1e6;

  ## 递归时调用记忆化版本
  z = foo2 (x, y);
endfunction

CacheSize 可以在预期有大量函数调用时(例如在递归函数内部)选择性地增大。如果超过 CacheSize,记忆化表会被重新调整大小,从而引起性能下降。因此,增大 CacheSize 的作用类似于预分配,可以加速执行。

当不再需要记忆化表时,函数 clearAllMemoizedCaches 负责将其清除。

 
clearAllMemoizedCaches ()

清除所有记忆化缓存。

记忆化机制维护了若干内部表,记录哪些函数被哪些输入调用过。本函数用于清除这些表以释放内存,或重新开始。

另请参阅: memoize.


版权所有 © 2024-2026 Octave中文网

ICP备案/许可证号:黑ICP备2024030411号-4