[英]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.