为啥记忆化不是语言功能?

Posted

技术标签:

【中文标题】为啥记忆化不是语言功能?【英文标题】:why memoization is not a language feature?为什么记忆化不是语言功能? 【发布时间】:2010-12-28 07:49:05 【问题描述】:

我想知道...为什么我所知道的任何语言都没有原生地提供记忆功能?

编辑:澄清一下,我的意思是该语言提供了一个关键字来将给定的函数指定为可记忆的,而不是每个函数都“默认”自动记忆,除非另有说明。例如,fortran 提供关键字 PURE 来指定特定的函数。我猜编译器可以利用这些信息来记忆调用,但我忽略了如果你声明 PURE 一个具有副作用的函数会发生什么。

【问题讨论】:

对于像我这样不知道它是什么的人:在计算中,记忆是一种优化技术,主要用于通过函数调用避免重复计算先前处理的结果来加速计算机程序输入。 谢谢科佩尔。的确,我应该更说教一点。 因为他们没有收到备忘录 :-) 您的意思是“图书馆”而不是“语言功能”吗?您是否要求自动记忆每个功能? @bmargulies:可能他们以前听说过,但他们的大脑并没有自动记住这个定义。 【参考方案1】:

为了让记忆化作为一种​​语言功能发挥作用,有几个要求。

    编译器需要识别有效的记忆函数(例如,它们是引用透明的)。 运行时必须能够在不降低整体性能的情况下智能地选择候选记忆。

在另一种语言中有一些假设,但如果我们可以通过即时编译 Java VM 中的热点来提高性能,那么我们肯定可以编写一个自动记忆系统。

虽然我认为这在理论上是有可能在一种语言(尤其是解释型语言)中获得性能提升,但我认为这是一个值得研究的领域。

【讨论】:

【参考方案2】:

您的问题也为您学习更多语言提供了解决方案。我认为 Lisp 支持记忆,我知道 Mathematica 支持。

【讨论】:

我知道六七个,但我不喜欢纯功能性的(因此没有 lisp),也不喜欢你必须花大价钱才能访问的东西(无论如何我都不需要它) ;) 我很抱歉,因为这个线程很旧,但我必须这样做。 lisp 不是纯函数式的。它是功能性的,但不纯【参考方案3】:

我真的认为这样的选择应该是。

在数据处理任务中,有一个不可变的输入数据(例如,作为时间序列,在给定时间内,只要一个值已知,它就永远不会改变)。考虑到今天 RAM 的可负担性,如果函数结果仅依赖于此类不可变数据,则将其记忆而不是每次需要时都重新读取是合理的。目前我(在 Scala 和 C# 中)手动引入内存存储表并编写 3 个函数而不是一个 - 一个从文件/db/ws 读取值,一个将其存储到内存表中,一个用于包装它们并从内存中读取(如果可用)或调用原始函数(如果没有)。我认为这可以而且应该作为关键字实现并在幕后完成。

【讨论】:

【参考方案4】:

因为编译器必须发出语义正确的程序。除非它是referentially transparent,否则你不能在不改变程序语义的情况下记忆一个函数。在大多数编程语言中,并非所有函数都是引用透明的(纯函数式编程语言除外),因此您无法记住所有内容。但是需要一种机制来检测引用透明度,这太难了。

【讨论】:

有时人们会无缘无故地投反对票,我想这只是为了好玩,或者他们是机器人。 你必须有一些代表才能投票 - 不太可能是机器人。【参考方案5】:

因为您不应该将某些东西作为语言特性来实现,因为它可以很容易地在语言本身中实现。记忆功能属于库,这正是大多数语言所放置的地方。

【讨论】:

您可以通过虚拟表等在 C 中实现面向对象的编程,但这并不妨碍 C++ 的创建... 我的答案中的“轻松”一词并非偶然出现。如果您打算复制 C++ 的实际功能,而不仅仅是创建浅层模仿,那么您不能简单地使用这些 C++ 功能“通过虚拟表”扩展 C。您将有效地重写 C++ 编译器的一半。相比之下,一般的记忆功能是任何体面语言的几行代码。为此添加关键字是个坏主意。我将把它留给 Lisp 社区来解释原因。 C++ 的一个关键概念(甚至超过 OO,我想说,因为它是不符合 Kay 定义的“OO”的一个特殊品牌)是“只为你付出的use”,这在 C 之上是很难做到的,至少在任何合理的语法下(因为它的宏语言太弱了)。相比之下,记忆化几乎可以用任何语言轻松完成,并且增加的句法开销最小。 “大多数语言”在图书馆里真的有这个吗?顺便说一句,我想不出一种语言具有用于记忆的标准库设施。 “一个通用的记忆功能是任何体面语言的几行代码”:我想我不知道任何体面的语言......好吧,也许是 Haskell,但我为什么要写一个记忆库而它已经内置了?【参考方案6】:

您希望从记忆中获得的可能与编译器记忆选项提供的不同。

您可能知道仅记住最近 10 个左右不同的计算值是有利可图的,因为您知道如何使用该函数。

您可能知道只记住最后 2 或 3 个值才有意义,因为您永远不会使用比这更早的值。 (想到斐波那契数列。)

您可能会在某些运行中生成很多值,而在其他运行中生成的值很少。

您可能想“丢弃”一些记忆值并重新开始。 (我用这种方式记忆了一个随机数生成器,所以我可以回放构建某个结构的随机数序列,而该结构的其他一些参数已经改变。)

作为优化的记忆化依赖于搜索记忆值比重新计算值便宜得多。这又取决于输入请求的顺序。这对记忆数据库有影响:它是使用堆栈、所有可能输入值的数组(可能非常大)、桶哈希还是 b 树?

记忆编译器必须要么提供“一刀切”的记忆,要么必须提供许多可能的替代方案,以及控制替代方案的参数。在某些时候,每个人都更容易要求用户提供自己的记忆。

【讨论】:

仍然认为有一个存储所有值或指定的默认值是有意义的。在目标函数上方有一种注释,比如。 @Memo(1..10) def foo(i:Int)【参考方案7】:

在 Haskell 中,memoization is automatic 用于您定义的不带参数的(纯)函数。 Wiki 中的斐波那契示例实际上是我能想到的最简单的可演示示例。

Haskell 可以做到这一点,因为您的纯函数被定义为每次产生相同的结果;当然,依赖副作用的一元函数不会被记忆。

我不确定上限是多少——显然,它不会记住超过可用内存。而且我也不确定memoization是否发生在编译时(如果值 can 在编译时确定),或者它是否 always 首先发生函数被调用的时间。

【讨论】:

【参考方案8】:

Clojure 有一个 memoize 函数 (http://richhickey.github.com/clojure/clojure.core-api.html#clojure.core/memoize):

memoize
function

Usage: (memoize f)

Returns a memoized version of a referentially transparent function. The
memoized version of the function keeps a cache of the mapping from arguments
to results and, when calls with the same arguments are repeated often, has
higher performance at the expense of higher memory use.

【讨论】:

我不确定这算不算。他特别说“语言功能”不是“在库中实现的”。尽管在任何 Lisp 中,语言特性和库实现之间的差异很小,所以无论如何都要 +1。 @A. Levy:另外,它来自clojure.core,这基本上意味着它是一种内置的语言功能,尽管不如 10 种左右的“特殊形式”中的一种那么基本。正如你所说,Lisp 中的规则是不同的 :)【参考方案9】:

换个问题。为什么应该?正如有人所说,它可以放在库中,因此无需向语言添加语法,它仅可用于难以自动识别的纯函数(除非您强制程序员对其进行注释)。也很难确定记忆化是否会加快速度。我不认为这是编程语言的理想特性。

【讨论】:

【参考方案10】:

A) 记忆以空间换时间。我想这可能会变成一个相当未绑定的属性,从某种意义上说,必须存储的数据程序或库的数量可能会很快消耗大部分内存。

对于几种语言,记忆化很容易实现,也很容易根据给定的要求进行定制。

以对大量文本进行自然语言处理为例,您不想一遍又一遍地计算文本的基本属性(字数、频率、共现...)。在这种情况下,与内存缓存相比,与对象序列化相结合的记忆化可能会很有用,因为您可能会在未更改的语料库上多次运行应用程序。

B) 另一个方面:所有函​​数或方法对于相同的给定输入产生相同的输出是不正确的。无论如何,一些用于记忆的关键字或语法是必要的,以及配置(内存限制,失效策略,......)......

【讨论】:

【参考方案11】:

并非所有语言本身都支持函数装饰器。我想这将是一种更通用的支持方法,而不仅仅是支持记忆化。

【讨论】:

以上是关于为啥记忆化不是语言功能?的主要内容,如果未能解决你的问题,请参考以下文章

无法实例化邮件功能。为啥会出现这个错误

JavaScript语言精粹

ASP.NET编译成功后,为啥有些功能不会执行?麻烦高手解答!

P1514 引水入城 [记忆化搜索]

接口与归一化设计

什么是LMDB闪电记忆映射数据库