Clojure风格:无意识地或冗余地调用'vec'?

时间:2016-12-29 23:06:35

标签: clojure

我在应用程序中有一个函数foo,其目的是conj一个元素到一个顺序集合的末尾。这些系列在其他功能上很远。有时集合是向量,有时它们是列表或惰性序列,方便了它们的功能。 Perforce,foo返回一个同样由其他下游函数使用的向量,这些函数可能会或可能不会关心它们获得什么样的顺序集合。

foo新数据之前,将xs的参数(vec xs)强制转换为conj的向量很容易。我的问题是,如果xs已经成为一个向量,我是否支付了可避免的(并且可能是多项式!)价格,或者vec及其被调用者“智能”是否足以绕过冗余分配?

如果clojure.lang.LazilyPersistentVector/create的参数是vec而不是vector?source for vec会显示最终调用clojure.lang.IObj。在我挖掘答案的那一刻,我认为在SO中提出一个关于设计意图的问题可能更聪明,而不是深入研究未来可能会改变的实施内容。

1 个答案:

答案 0 :(得分:4)

我找到了一个涵盖该主题的interesting blog post,似乎是由Clojure语言开发人员撰写的。重要的部分是向下的方式:

在...上调用vec

  

IPersistentVector - 如果它已经是一个向量,则删除meta并返回一个新实例。如上所述,这种情况发生了很多,现在是快速恒定时间操作,而不是线性时间操作。

除非我错过了一些上下文,否则这似乎表明vec已经被优化(截至2015年1月),对已经是向量的集合进行了恒定时间操作。

为了完整起见,我使用Criterium运行测试(感谢@Thumbnail的想法)。我在带有M3处理器的Surface Pro 4上运行它,这是非常弱的。期待你的时间缩短:

(let [small-v (into [] (range 1000))
      large-v (into [] (range 1000000))]
  (do
    (c/bench (vec small-v))
    (println "-----")
    (c/bench (vec large-v))))
Evaluation count : 2902458480 in 60 samples of 48374308 calls.
             Execution time mean : 18.612868 ns ; <----------
    Execution time std-deviation : 1.153643 ns
   Execution time lower quantile : 17.731625 ns ( 2.5%)
   Execution time upper quantile : 22.199789 ns (97.5%)
                   Overhead used : 3.054999 ns

Found 6 outliers in 60 samples (10.0000 %)
    low-severe   6 (10.0000 %)
 Variance from outliers : 46.7497 % Variance is moderately inflated by outliers
-----
Evaluation count : 3122779260 in 60 samples of 52046321 calls.
             Execution time mean : 16.303825 ns ; <----------
    Execution time std-deviation : 0.614467 ns
   Execution time lower quantile : 15.727943 ns ( 2.5%)
   Execution time upper quantile : 17.949363 ns (97.5%)
                   Overhead used : 3.054999 ns

Found 5 outliers in 60 samples (8.3333 %)
    low-severe   1 (1.6667 %)
    low-mild     4 (6.6667 %)
 Variance from outliers : 23.8541 % Variance is moderately inflated by outliers

时间几乎相同(彼此相差2ns)。这似乎证实了上述引用。