【问题标题】:How to combine non-tail recursion + effective and synchronized memoization + bounded stack consumption in Clojure?如何在 Clojure 中结合非尾递归 + 有效和同步的记忆 + 有界堆栈消耗?
【发布时间】:2016-08-10 18:16:32
【问题描述】:

如何在 Clojure 中将非尾递归与同步记忆和有限堆栈消耗(因此没有堆栈溢出风险)结合起来?同步记忆是指记忆/缓存必须在线程之间同时有效地共享。

我的具体情况如下:

; g() is non recursive
; i is an integer
; h is a hash with int keywords and vector of ints values
; w is a hash with int keywords and int values
(defn g [i h w]
  (filter
    #(-> (w %)
         (= i))
    (h i)))

; f is recursive, recurses non-trivially (non-tail, multiple times)
; TODO: be memoizable (ideally in a synchronized way, for parallelism)
; TODO: pose no risk stack overflow
(defn f [i h w]
  (if (nil? (h i))
    0
    (let [part_sum
      (map                     ; will change this map to pmap or pvmap
        #(f % h w)
        (g i h))]
      (-> (reduce + part_sum)
          (/ 2)
          (+ 1)))))

; trivial, shown for completeness
(defn ff [i h w]
  (-> (f i h w)
      (- 1)
      (* 2)
      (max 0)))

【问题讨论】:

  • 关于记忆:memoize from core lib uses atom under the hood,所以它应该是线程安全的,我相信
  • 关于避免堆栈溢出:我会说,这是不是 clojure 特定的一般问题。 Here你可以找到一些通用的方法来解决这个问题。

标签: concurrency clojure synchronization stack-overflow memoization


【解决方案1】:

幸运的是,这些问题可以独立解决:

  1. 一致的共享 memoize 缓存
  2. 不爆栈的非尾递归迭代

对于问题 1,您需要首先确定应该在什么时候填充缓存。是否应该在您开始计算函数时填充它。这意味着应该绝对保证每个函数只会运行一次,即使在第一个函数运行时进行了第二次调用。或者,如果您想允许对函数的两个调用同时发生,并且只将其中一个存储到缓存中。稍有不同的是,您只需将返回的最后一个结果存储到缓存中。

如果你只是调用,最后一种方法是你默认得到的

(def memoized-function (memoize function-name))

ans 对于几乎所有情况都足够了。如果您需要其他选项,则让您希望记忆的函数返回 future 而不是结果,并且只需 deref 在使用它们之前从缓存中获取的值。

对于选项二,内置的trampoline 函数允许您拥有常量堆栈非尾递归函数。您更改函数以在基本情况下(递归完成时)返回一个不是函数(只是正常结果)的值,并在需要进一步递归时返回一个函数。然后trampoline 函数反复“反弹”到函数中,直到一个值从另一侧掉出。它看起来像这样:

user> (defn foo-helper [x]
        (let [result 
              (if (pos? x)
                #(foo-helper (dec x))
                x)]
          (println "foo" x)
          result))
#'user/foo-helper
user> (trampoline foo-helper 4)
foo 4
foo 3
foo 2
foo 1
foo 0
0

因此,您可以将 Clojure 中的正常缓存与正常的 trampline 函数调用结合起来,而不必担心“线程安全”

【讨论】:

    猜你喜欢
    • 2011-03-28
    • 2011-02-04
    • 2011-04-23
    • 1970-01-01
    • 1970-01-01
    • 2019-09-30
    • 1970-01-01
    • 2012-09-20
    • 2012-11-12
    相关资源
    最近更新 更多