【问题标题】:Head retention in ClojureClojure 中的头部保留
【发布时间】:2012-11-08 08:46:22
【问题描述】:

阅读“Clojure 编程”(第 98 页)中关于头部保留的段落,我无法弄清楚 split-with 示例中发生了什么。我试过用 repl 做实验,但它让我更加困惑。

(time (let [r (range 1e7) 
            a (take-while #(< % 12) r)
            b (drop-while #(< % 12) r)]
        [(count a) (count b)]))
"Elapsed time: 1910.401711 msecs"
[12 9999988]

(time (let [r (range 1e7) 
            a (take-while #(< % 12) r)
            b (drop-while #(< % 12) r)]
        [(count b) (count a)]))
"Elapsed time: 3580.473787 msecs"
[9999988 12]

(time (let [r (range 1e7) 
            a (take-while #(< % 12) r)
            b (drop-while #(< % 12) r)]
        [(count b)]))
"Elapsed time: 3516.70982 msecs"
[9999988]

从上一个示例中可以看出,如果我不计算 a,则耗时会以某种方式增加。我想,我在这里错过了一些东西,但是什么?

【问题讨论】:

标签: clojure


【解决方案1】:

Count 是 O(1)。这就是为什么您的测量不依赖于它。

【讨论】:

  • 列表计数实际上是 O(1),但根据“Clojure Programming”,seq 并不是真正的列表,获取 seq 的长度是有代价的。
  • 对不起。我误导了你。是的,在您的情况下,它是 LazySeq 并且“计数”是 O(n)。在我的机器上(计数 a)花了 0.019 毫秒。因此,对于您的测量,它仍然看起来像 O(1)。
  • 请尝试检查 (count b),因为在我的情况下它是 (count b),而不是 (count a)。
  • (count a)之前基本使用(count b)导致总时间增加
【解决方案2】:

count 函数对于 Counted 集合是 O(1),其中包括向量和列表。

另一方面,序列是not counted,这使得它们的count O(n)。这里重要的部分是函数take-whiledrop-while 返回序列。他们也是懒惰的事实并不是这里的主要因素。

【讨论】:

    【解决方案3】:

    当使用时间作为基准时,多次运行测试以获得准确的结果

    user> (defn example2 [] (let [r (range 1e7)                                             
                  a (take-while #(< % 12) r)                                     
                  b (drop-while #(< % 12) r)]                        
                 [(count a) (count b)]))
    #'user/example2
    
    user> (dorun (take 1000 (repeatedly example2)))
    nil
    
    user> (time (example2))
    "Elapsed time: 614.4 msecs"
    [12 9999988]
    

    我归咎于运行时的差异,因为热点编译器尚未完全优化生成的类。我多次运行第一个和第二个示例并得到混合的相对结果:

    运行示例一两次:

    autotestbed.core> (time (let [r (range 1e7)                                                                
                                            a (take-while #(< % 12) r)                                     
                                                        b (drop-while #(< % 12) r)]                        
                                  [(count a) (count b)]))
    "Elapsed time: 929.681423 msecs"                                                                           
    [12 9999988]
    autotestbed.core> (time (let [r (range 1e7)                                                                
                                            a (take-while #(< % 12) r)                                     
                                                        b (drop-while #(< % 12) r)]                        
                                  [(count a) (count b)]))
    "Elapsed time: 887.81269 msecs"                                                                            
    [12 9999988]
    

    然后运行示例二几次:

    core> (time (let [r (range 1e7)                                                                
                      a (take-while #(< % 12) r)                                     
                      b (drop-while #(< % 12) r)]                        
                 [(count a) (count b)]))
    "Elapsed time: 3838.751561 msecs"                                                                          
    [12 9999988]
    core> (time (let [r (range 1e7)                                                                
                      a (take-while #(< % 12) r)                                     
                      b (drop-while #(< % 12) r)]                        
                 [(count a) (count b)]))
    "Elapsed time: 970.397078 msecs"                                                                           
    [12 9999988]
    

    有时第二个例子也一样快

    【讨论】:

      【解决方案4】:

      即使我们不使用此值,也会执行let 表单中的绑定。

      (let [x (println "Side effect")] 1)
      

      上面的代码打印“副作用”,并返回 1。

      在您的所有三个示例中,都以 let 形式使用了相同的绑定,所以我在这里看不出任何区别。顺便说一句,在我的机器上,你所有的 sn-ps 所用的时间大致相同。

      当你尝试这样的事情时真正的区别:

      (time (let [r (range 2e7) 
              a (take 100 r)
              b (drop 100 r)]
          [(count a)]))
      "Elapsed time: 0.128304 msecs"
      [100]
      
      (time (let [r (range 2e7) 
              a (take 100 r)
              b (drop 100 r)]
          [(count b)]))
      "Elapsed time: 3807.591342 msecs"
      [19999900]
      

      由于ba 是惰性序列,countO(n) 时间有效。但是在第一个示例中,我们不计算 b 的计数,因此它几乎立即起作用。

      【讨论】:

      • 在原始示例中,r ab 在 let 语句中立即绑定到惰性未评估序列。绑定这些名称不会导致对序列进行评估。
      【解决方案5】:

      显示的时间完全取决于系统.... 如果你重新执行它,它会为每次执行显示一些不同的经过时间

      【讨论】:

        猜你喜欢
        • 2013-04-06
        • 2018-08-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-23
        • 1970-01-01
        相关资源
        最近更新 更多