2024年Clojure状态调查!中分享您的想法。

欢迎!请查看关于页面以获得更多有关此内容的信息。

0
core.memoize

问题:函数A被记忆化。函数B(以及其他东西),在从不调用A的情况下,计算有效的A函数调用。我们希望在B中利用此效果以进一步减少A的计算。如果我们期望B比A执行得更快,我们也希望A的调用者等待相应的B运行。

在B中实现该功能的所需操作如下
(spoof A args calc)

其中,calc是一个零参数的fn,它产生一个等于(apply A args)的值。这使得调用者能将calc中恰好的工作量投入calc,这样他预期会比A更快地执行。

Spoof返回calc返回的内容,或者在缓存命中时返回为args存储的内容。也就是说,如果曾经发生调用(A args)后的(spoof A args calc),calc就不会被调用。

在已经有了(A args)的结果且不想被(A args)的并发调用阻塞的情况下,所需操作如下

(spoof-unsynchronized A args val)

它立即返回val并尽快更新A的缓存。由于非同步欺骗是一个非常快的操作,它无论如何都与(A args)的调用存在竞争条件,因此无需阻塞(A args)的调用。((所以swap!直接对内部缓存状态进行操作就足够了))。

11 条回答

0

评论者:seancorfield

这看起来不像应该成为core.memoize库的一部分的东西,但在其之上构建这样的东西应该是可能的(现在还不是,对吗?)。

为了允许这一点,最小必要的更改是否是暴露底层的缓存(这样您可以直接针对缓存调用deref/swap!)?

0

评论者:lgs32a

对于不同步的情况,是的;对于同步(有趣)的情况,可能需要一些ność,我必须检查代码。

我不同意这种功能不应该内置。它相当通用,允许非常灵活的用户定义的备忘录实现。

上面推荐的设计是来自Google Guava缓存。

https://google.github.io/guava/releases/23.0/api/docs/com/google/common/cache/Cache.html#get-K-java.util.concurrent.Callable-

它允许像这样进行备忘录。你只需要调用
(.get c (链接:arg1] (函数) "为(link:arg1)计算")) ;; 注意通过那个功能实现自定义备忘录有多简单!

运行fn同步(只一次)所需的逻辑在我们的例子中是在core.memoize中实现的。因此,如果core.memoize公开类似的设施,我相信这将是一个非常通用的API,允许在上面构建更多的东西(自定义备忘录实现)。

它还有一个.put,用于处理不同步的情况。

0

评论者:seancorfield

有趣。我下一个问题本来是想问“你是从哪里得到这个想法的?”

{{core.memoize}}背后的机制以类似的方式工作,有一个* }函数处理thunk等,但是{{build-memoizer}}函数封装了所有这些,还有{{PluggableMemoizer}}类型。

我需要认真考虑一下。缓存本身附着在{{build-memoizer}}返回的函数对象的元数据上,所以从技术上讲,如果你想自己编程,所有这些都已经是公开的。

0

评论者:lgs32a

我的理解是,我需要通过* }以一个修复的(忽略参数)fn来调用private。

真的不能称之为公开:)

0

评论者:lgs32a

我还需要复制粘贴第209行之后的代码,用于处理一个罕见的定时问题。

0

评论者:lgs32a

或者,我是不是理解错了,你建议如果要修复程序的话?

0

评论者:seancorfield

我的意思是,如果需要检查一个参数列表的键是否存在,或通过上面提到的thunk更新缓存为新值,原始缓存就存在于元数据中。可以直接在缓存上调用{{core.cache/through}}(或者更有可能是{{core.cache/through-cache}}),使用一个thunk(尽管只需要一个参数,你可以忽略它)。

(swap! (:clojure.core.memoize/cache (meta the-fn-var)) clojure.core.cache/through-cache [arg1] (fn [_] "为[arg1]计算"))

任何使用TTL缓存的用户都必须处理访问期间可能出现的边缘情况,即缓存驱逐(除非他们不介意返回{{nil}})。因此,执行上述swap!操作的{{spoof}}函数可能需要重试thunk调用--是否在这一点更新缓存取决于你(即,如果你的{{spoof}}调用期间发生了缓存驱逐,后续调用会有什么行为?)。

关键是{{spoof}}需要实际的缓存来完成任务。缓存函数只能通过闭包访问到该缓存,因此{{spoof}}必须在该缓存变量上操作。

这澄清了我建议的内容吗?

0

评论者:lgs32a

Spoof还会利用d-lay中的通过*为其锁定功能。它可能需要对支持thunk进行微调。

上面推荐的设计故意采用了缓存函数,以便提取::cache。

关于我将要解决的TTL修复,请再让我看看。如果你对包括spoof/spoof-aysnc...的补丁感兴趣,请告诉我。

0

评论者:seancorfield

我讨厌这些名字,但我会考虑一个补丁,它暴露了足够多的机制,以便用户更轻松地在库外部构建自己的版本。

0

评论者:lgs32a

我选择了名字,以符合当前库的设计。在Guava提供了强大的原语并声称具有通用的低级名称的情况下,这个库是按照它对用户想要用它做什么有强烈假设的风格编写的。它使你能够立即有一个带有缓存的缓存函数。

这就是我选择只描述你为什么使用它们/附加/名称的原因。可能“spoof”这个词不如其他词好 - 我不是母语人士。

core.cache只公开了缓存的数据结构,但并没有公开一个适当的引用类型来缓存计算(而不是值),就像Guava所做的那样。我认为core.cache的作者可能没有做到这一点,因为他们认为Clojure的引用类型已经足够用了。core.memoize为自己实现了这样一个引用类型作为内部实现。我不确定是否在库中公开这样的功能(这在外部memoization之外有很多用例)是否在了正确的地方。因此,对memoized函数的命名和包装明显表明,该功能是在memoization的范围内。

0
...