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中,他期望这个比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
by

评论者为:lgs32a

针对不同步的情况,是的;对于同步的(有趣)情况,可能需要通过恶意黑客手段,我需要查看代码。

我不同意这个功能不应该内置。它相当通用,并允许非常灵活的用户自定义记忆化实现。

上述推荐的设计是借鉴了Google Guavas的缓存。

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

它允许如下记忆化。你只需要调用
(.get c (link: arg1] (fn [) "calc for (link: arg1)")) ;; 注意使用这个功能实现自定义记忆化是多么简单!

运行fn同步(一次)所必需的逻辑在我们的cases中通过core.memoize实现。所以如果core.memoize公开了一个类似的功能,我相信这将是一个非常通用的API,它可以构建更多的东西(自定义记忆化实现)。

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

0
by

评论者:seancorfield

很有意思。我的下一个问题是:“你是怎么想到这个的?”。

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

我需要仔细思考一下。缓存本身附加到{{build-memoizer}}返回的函数对象的元数据上,因此,如果你自己想要编程实现,技术上已经全部暴露了。

0
by

评论者为:lgs32a

我的理解是,我需要用修改过的忽略参数的fn调用通过*。

真的调不了那个公开的:

0
by

评论者为:lgs32a

我还需要在209行之后复制粘贴处理罕见时间问题的代码。

0
by

评论者为: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}}必须作用于持有记忆化函数的{{Var}}。

这样解释清楚了吗?

0

评论者为:lgs32a

{{Spoof}} ще използва вътрешния through* за своите механизми за блокиране в d-lay. Може да се наложи да направите малки корекции за поддръжка на thunk.

Рекомандираният по-горе дизайн умишлено приема不是一个记忆化的函数, за да може да извлече ::cache。

Относно fixing на TTL, трябва да го разгледам отново. Моля, уверете ме ако Ви интересува.patch с spoof/spoof-async...

0

评论者:seancorfield

我不喜欢这些名字,但我会考虑一个补丁,它公开足够的机制,使用户能够更容易在外部构建自己的版本。

0

评论者为:lgs32a

我为选择的名字是为了让它们符合当前库的架构。在Guava给你提供强大的原语,并声明它们有通用的底层名称的情况下,这个库是以它在用户如何使用它方面有一定假设的风格编写的。它使得你直接使用附有缓存的记忆化函数。

这就是为什么我选择了只描述你会用它们的原因的名字。可能有一些比"spoof"更好的单词 - 我不是英语母语者。

core.cache 只暴露了缓存的数据结构,但并未暴露一个适当的引用类型来缓存计算(而非值),类似于 Guava 所做的。我认为 core.cache 的作者没有做这件事情,因为他们认为 Clojure 的引用类型就足够了。core.memoize 将这种引用类型作为一个内部实现。我不确定暴露这种具有大量用例(而不仅是缓存)的功能是否应该放在这个库中的正确位置。因此,命名和包装 memoized 函数明确指出,这种功能应在缓存作用的范围内。

0
参考: https://clojure.atlassian.net/browse/CMEMOIZE-24 (由 lgs32a 报告)
...