clojure - 规范/有效期的评估时间?呈指数增长

标签 clojure clojure.spec

我正在使用 clojure.spec解析 DSL。不幸的是,测试是否符合我的规范的计算时间似乎呈指数增长。我想了解原因,以及如何解决。

这是规范的样子:

(spec/def ::settings map?)

(spec/def ::header (spec/spec
                    (spec/cat :prefix #{:begin-example}
                              :label string?
                              :settings (spec/? ::settings))))

(def end-block [:end-example])

(spec/def ::not-end (partial not= end-block))

(spec/def ::end #{end-block})

(spec/def ::block (spec/cat
                   :header ::header
                   :data (spec/* ::not-end)
                   :suffix ::end))

(spec/def ::form (spec/alt :block ::block
                           :form any?))

(spec/def ::forms (spec/* ::form))

为了执行规范,我编写了一个小函数,为规范生成有效数据,并编写了一个大小参数来控制数据的大小:
(defn make-sample-data [size]
  (transduce
   (comp (take size)
         cat)
   conj
   []
   (repeat [:a 1 :b :c [:begin-example "a" {:indent 4}] :d :e [:end-example] 9])))

(make-sample-data 1)
;; => [:a 1 :b :c [:begin-example "a" {:indent 4}] :d :e [:end-example] 9]

(make-sample-data 2)
;; => [:a 1 :b :c [:begin-example "a" {:indent 4}] :d :e [:end-example] 9 :a 1 :b :c [:begin-example "a" {:indent 4}] :d :e [:end-example] 9]

现在我正在执行此代码:
(dotimes [i 13]
  (assert (time (spec/valid? ::forms (make-sample-data i)))))

产生以下输出:
"Elapsed time: 0.077095 msecs"
"Elapsed time: 0.333636 msecs"
"Elapsed time: 0.864481 msecs"
"Elapsed time: 2.198994 msecs"
"Elapsed time: 4.432004 msecs"
"Elapsed time: 9.026142 msecs"
"Elapsed time: 17.709151 msecs"
"Elapsed time: 35.201316 msecs"
"Elapsed time: 73.178516 msecs"
"Elapsed time: 138.93966 msecs"
"Elapsed time: 288.349616 msecs"
"Elapsed time: 569.471181 msecs"
"Elapsed time: 1162.944497 msecs"

在我看来,对于 size 参数的每一步,计算时间都会加倍。

我的问题是:如何修改我的规范,以便验证时间与我的数据大小成线性关系?

最佳答案

我猜测性能问题来自贪婪的分支正则表达式规范与 any? 的组合。谓词。
any?的使用在 s/alt :form regex 分支对我来说很突出。我想规范可能正在评估 s/alt 的每个分支贪婪地/穷尽地然后回溯,和 any?匹配所有内容,包括与您的 :block 匹配的值分支。 (请注意,无论 :form any? 分支是在 :block 分支之前还是之后定义,规范都符合。)

如果您可以使用比 any? 更具体的谓词在您的顶级 s/alt :form分支,您应该会看到很大的改进。为简洁起见,我内联了规范定义:

(s/def ::forms
  (s/*
    (s/alt :block
           (s/cat :header (s/spec
                            (s/cat :prefix #{:begin-example}
                                   :label string?
                                   :settings (s/? map?)))
                  :data (s/* #(not= % [:end-example]))
                  :suffix #{[:end-example]})
           :form
           (s/nonconforming ;; don't tag results
             (s/or :keyword keyword?
                   :number number?)))))

(time (s/valid? ::forms (make-sample-data 1000)))
"Elapsed time: 84.637513 msecs"
=> true

请注意,允许集合谓词(例如 coll?vector? )到该 :form分支会像 any? 一样降低性能.我想这是因为相同的值匹配 s/alt 的两个分支.

关于clojure - 规范/有效期的评估时间?呈指数增长,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54461834/

相关文章:

Clojure 反向展平列表。例如。 '(1 2 3 4 5 6) to ' ((1 2) (3 4) (5 6)

clojure - 在 clojure.spec 中提供默认值

clojurescript - 将宏应用于向量

Clojure、Java Interop 和代理方法

java - 使异常信息更丰富

clojure - Clojure 中嵌套向量的映射函数

clojure - 如何检查spec/col-of中的不同ID

clojure - 将Clojure Spec与Datomic实体一起使用

clojure - clojure.spec 中的禁止键

clojure - Leiningen 中是否有独立的 Clojure 包?