Haskell 是什么使透镜的长度无效?
我在Haskell 是什么使透镜的长度无效?,haskell,haskell-lens,Haskell,Haskell Lens,我在lens中注意到这一点,声称lengthOf可能“相当低效”: 它会是无症状的低效吗?尽管是严格的左折,它是否会泄漏空间,或者GHC是否会生成太多的“繁忙”代码?更新:见下文--长度的性能不佳似乎只是在基准测试时缺乏对列表案例的专门化 正如@WillemVanOnsem所指出的,我认为这个评论主要是指这样一个事实:这种特殊的方法——使用计数器遍历容器的元素——对于使用其他方法返回长度的容器来说效率低下。例如,对于非常大的向量v,从技术上讲,您可以使用导线v的长度,但是Data.vector.
lens
中注意到这一点,声称lengthOf
可能“相当低效”:
它会是无症状的低效吗?尽管是严格的左折,它是否会泄漏空间,或者GHC是否会生成太多的“繁忙”代码?更新:见下文--长度的性能不佳似乎只是在基准测试时缺乏对列表案例的专门化
正如@WillemVanOnsem所指出的,我认为这个评论主要是指这样一个事实:这种特殊的方法——使用计数器遍历容器的元素——对于使用其他方法返回长度的容器来说效率低下。例如,对于非常大的向量v
,从技术上讲,您可以使用导线v
的长度,但是Data.vector.length v
会快得多
另一方面,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
]
显示长度
大约比长度
快15倍。(我在所有测试中都使用了GHC 8.4.3和-O2
)
请注意,这种差异不是列表融合的结果(因为在使用whnf
调用时,Prelude.length
案例中没有融合)
它实际上是将代码专门化为列表的结果。尽管Prelude.length
适用于任何可折叠的
,但列表实例使用特定于列表的实现,该实现本质上等同于:
myLength :: [a] -> Int
myLength xs = lenAcc xs 0
where lenAcc [] n = n
lenAcc (_:ys) n = lenAcc ys (n+1)
(我没有确认这是正在使用的实现,但是myLength
的性能几乎与Data.List
相当)
myLength
的核心在循环中使用非固定整数,该循环直接匹配列表构造函数,或多或少类似于:
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)
它生成了如下所示的核心。与上面相同,还有一个额外的参数,它本质上是一个强制转换标识函数:
lenAcc'
lenAcc'
= \n id' xs ->
case xs of {
[] -> id' (I# n);
(:) _ xs' -> lenAcc' (+# n 1#) id' xs'
}
我无法对它进行基准测试,但它可能会非常快
因此,lengthOf traverse
可以优化到几乎与Prelude.length
一样快,但这取决于它的使用方式,它可能会使用一个非常低效的实现。更新:见下文--的lengthOf的低性能似乎只是在基准测试时缺乏对列表案例的专门化
正如@WillemVanOnsem所指出的,我认为这个评论主要是指这样一个事实:这种特殊的方法——使用计数器遍历容器的元素——对于使用其他方法返回长度的容器来说效率低下。例如,对于非常大的向量v
,从技术上讲,您可以使用导线v
的长度,但是Data.vector.length v
会快得多
另一方面,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
]
显示长度
大约比长度
快15倍。(我在所有测试中都使用了GHC 8.4.3和-O2
)
请注意,这种差异不是列表融合的结果(因为在使用whnf
调用时,Prelude.length
案例中没有融合)
它实际上是将代码专门化为列表的结果。尽管Prelude.length
适用于任何可折叠的
,但列表实例使用特定于列表的实现,该实现本质上等同于:
myLength :: [a] -> Int
myLength xs = lenAcc xs 0
where lenAcc [] n = n
lenAcc (_:ys) n = lenAcc ys (n+1)
(我没有确认这是正在使用的实现,但是myLength
的性能几乎与Data.List
相当)
myLength
的核心在循环中使用非固定整数,该循环直接匹配列表构造函数,或多或少类似于:
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)
它生成了如下所示的核心。与上面相同,还有一个额外的参数,它本质上是一个强制转换标识函数:
lenAcc'
lenAcc'
= \n id' xs ->
case xs of {
[] -> id' (I# n);
(:) _ xs' -> lenAcc' (+# n 1#) id' xs'
}
我无法对它进行基准测试,但它可能会非常快
因此,lengthOf traverse
能够被优化为几乎与Prelude.length
一样快,但取决于它的使用方式,它可能最终使用一个非常低效的实现。它在线性时间内运行,计算长度通常相当长。它在线性时间内运行,这通常是相当长的计算长度。