【问题标题】:Eval times for this function alternate b/w 85 nanosec and 10 sec (!?)此函数的评估时间交替 b/w 85 纳秒和 10 秒(!?)
【发布时间】:2014-07-09 05:50:32
【问题描述】:

目标

我试图弄清楚为什么我创建的函数items-staged-f 的评估时间既长又短。

你说奇怪?

我说“奇怪”是因为:

  • (time (items-staged-f)) 产生 1.313 msecs
  • (time (items-staged-f)) 第二次产生0.035 msecs(这并不奇怪,因为结果是一个惰性序列,它一定是被记忆的)
  • Criterium 基准测试系统报告它采用85.149767 ns(这不足为奇)

然而……

  • 在 REPL 中实际评估 (items-staged-f) 所需的时间约为 10 秒。这甚至在它打印任何东西之前。我最初认为它可能需要很长时间,因为它准备打印到 REPL,因为它是一个长而复杂的数据结构(嵌套映射和向量在惰性序列中),但奇怪的是结果甚至不会开始打印到 10 秒后(据说)需要 85 纳秒。莫非是在预计算如何打印数据结构?
  • (time (last (items-staged-f))) 产生 10498.16 msecs(尽管这可能会在 20 秒左右变化),原因可能与上述相同。

现在是代码...

函数items-staged-f 的目标是可视化需要做什么,以便对会计数据库中的库存项目进行一些必要的更改。

items-staged-f 中引用的不熟悉函数可以在下面找到。

(defn items-staged-f []
  (let [items-0 (lazy-seq (items-staged :items))
        both-types? #(in? % (group+line-items))
        items-from-group #(get items-0 %)
        replace-subgroups
          (fn [[g-item l-items :as group]]
            (let [items-in-both
                    (->> l-items
                         (map :item)
                         (filter both-types?))]
              (->> (concat
                        (remove #(in? (:item %)  items-in-both) l-items)
                        (mapcat items-from-group items-in-both))
                   (into [])
                   (assoc group 1))))
        replaced (map replace-subgroups items-0)]
   replaced))

items-staged 是输出items-staged-f 操作的原始数据的函数。 (items-staged :items) 输出带有字符串键(组项)的映射,其值是映射向量(子项列表):

{"786M" ; this is a group item
 ; below are the sub-items of the above group item
 [{:description "Signature Collection Item", :item "4X1"}
  {:description "Cookies, Inc. Paper Wrapped", :item "65G7"}
  {:description "MyChocolate 5 oz.", :item "21F"}]}

请注意,items-staged-f 的输出在结构上与items-staged 的输出几乎相同,只是它是一个惰性向量序列,而不是一个带有 hash-map-entries 的 hash-map,如下所示预期通过在哈希映射上调用 map 函数。

in? 是一个谓词,用于检查对象是否在给定集合中。例如,(in? 1 [1 2 3]) 的计算结果为 true

group+line-items 是一个函数,它输出我希望消除的某些重复项的惰性序列。例如,(group+line-items) 计算结果为:("428X" "41SF" "6998" "75D22")

注意事项

VisualVM 1.3.8 说 clojure.lang.Reflector.getMethods() 时钟在 28700 毫秒 (51.3%),clojure.lang.LineNumberingPushbackReader.read() (这是因为 REPL 中的输出?) 9000 毫秒 (16.2%),clojure.lang.RT.nthFrom() 7800 毫秒 (13.9%)。

但是,当我在 REPL 中单独评估惰性序列 (nth items-staged-f n) 的每个元素时,只有 clojure.lang.LineNumberingPushbackReader.read() 会上升。调用以 32 为增量增加,这是惰性序列分块大小。其他方法/功能所用的时间可以忽略不计。

另一个考虑因素是items-staged 是一个最终从 Excel 文件中提取数据的函数(通过Apache POI 读取)。但是,Excel 文件中的原始数据存储为 var,所以这应该不是问题,因为它只会在被记忆之前计算一次(我认为)。


感谢您的帮助!

附录

一旦我使用doall 强制实现惰性序列(我认为它正在实现),Criterium 现在说该函数需要11.370356 sec 来评估,不幸的是这是有道理的。重构后我会重新发布。

【问题讨论】:

    标签: performance clojure benchmarking read-eval-print-loop


    【解决方案1】:

    根据定义,惰性序列仅在需要时才计算其元素。打印到 REPL 或请求 last 元素都强制实现。不会为产生惰性序列的函数调用计时。

    (defn slow-and-lazy [] (map #(do (Thread/sleep 1000) (inc %)) (range 10)))
    
    user=> (time (slow-and-lazy))
    "Elapsed time: 0.837002 msecs"
    (1 2 3 4 5 6 7 8 9 10) ; printed 10 seconds later
    
    user=> (time (doall (slow-and-lazy)))
    "Elapsed time: 10000.205709 msecs"
    (1 2 3 4 5 6 7 8 9 10)
    

    (time (slow-and-lazy)) 的情况下,slow-and-lazy 快​​速返回一个未实现的惰性序列,time 完成,打印经过的时间并将这种情况下的未实现结果传递给 REPL。然后,REPL 尝试打印序列。为此,它必须实现序列。


    话虽如此,10 秒对于计算机来说是永恒的,所以这确实值得检查/分析。我建议将您的代码重构为更小的独立函数。特别是,数据应该作为参数传入。一旦确定了瓶颈(与doall 一起强制实现!),然后考虑发布一个新问题。由于无法准确判断这段代码发生了什么,或者items-staged 中的 IO 是否是真正的瓶颈,但似乎仍有改进的余地。

    【讨论】:

    • 感谢您的帮助。我不知道(time (slow-and-lazy)) 会立即打印时间,但会意识到打印它的整个序列。我将重构代码并发布更新。
    • @alexandergunnarson 如果它解决了当前的问题,您可以接受这个答案(答案左侧的复选标记)。然后,将您发现的任何子问题作为新的单独问题发布。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-25
    • 2019-08-01
    • 1970-01-01
    相关资源
    最近更新 更多