簡體   English   中英

是什么使lengthOf的鏡片效率低下?

[英]What makes lengthOf inefficient in lens?

我注意到lens這一說法 ,聲稱lengthOf可能“相當低效”:

-- /Note:/ This can be rather inefficient for large containers [...]
lengthOf :: Getting (Endo (Endo Int)) s a -> s -> Int
lengthOf l = foldlOf' l (\a _ -> a + 1) 0

可以漸近地效率低下嗎? 盡管嚴格折左,它會泄漏空間嗎?還是GHC會生成過多的“忙碌工作”代碼?

更新:參見下文-在進行基准測試時, lengthOf性能lengthOf似乎只是針對列表情況缺乏專業性。

正如@WillemVanOnsem所指出的那樣,我認為評論主要是指以下事實:對於使用其他方法返回長度的容器來說,這種特殊的方法(使用計數器運行容器的元素)效率不高。 例如,對於非常大的向量v ,從技術lengthOf traverse v ,您可以使用lengthOf traverse v ,但是Data.Vector.length v會快得多。

另一方面, lengthOf對於計算列表中的元素lengthOf 可能效率很低。 以下基准:

import Criterion.Main
import Control.Lens

l :: [Int]
l = replicate 1000000 123

main = defaultMain
  [ bench "Prelude.length"        $ whnf length l
  , bench "Control.Lens.lengthOf" $ whnf (lengthOf traverse) l
  ]

表明length大約比lengthOf快15倍。 (我在所有測試中都使用了帶有-O2 GHC 8.4.3。)

請注意,這種差異是不是名單融合的結果(因為有一個在沒有融合Prelude.length情況下,當whnf使用電話)。

這實際上是將代碼專門化為列表的結果。 即使Prelude.length適用於任何Foldable ,列表的實例也使用特定於列表的實現,該實現實質上等效於:

myLength :: [a] -> Int
myLength xs = lenAcc xs 0
  where lenAcc [] n = n
        lenAcc (_:ys) n = lenAcc ys (n+1)

(我沒有確定這是否正在使用實現,但是myLength性能幾乎與Data.List 。)

myLength的Core在直接與列表構造函數模式匹配的循環中使用未裝箱的整數,或多或少類似於:

lenAcc
  = \xs n ->
      case xs of
        [] -> n
        (:) _ xs' -> lenAcc xs' (+# n 1#)

原來,如果我在一個更現實的程序中使用lengthOf ,並且有足夠的空間以相同的方式專門處理列表:

import Control.Lens

l :: [Int]
{-# NOINLINE l #-}
l = replicate 1000000 123

myLength :: [a] -> Int
myLength = lengthOf traverse

main = print (myLength l)

它生成了如下的Core。 與上面相同,但具有一個額外的參數,該參數本質上是強制轉換標識函數:

lenAcc'
lenAcc'
  = \n id' xs ->
      case xs of {
        [] -> id' (I# n);
        (:) _ xs' -> lenAcc' (+# n 1#) id' xs'
      }

我無法對其進行基准測試,但可能很快。

因此,可以對lengthOf traverse進行優化,使其速度幾乎與Prelude.length一樣快,但是根據其使用方式,最終可能會使用效率非常低的實現。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM