Performance 为什么';列表和向量不一样吗?(哈斯克尔)
考虑以下基准:Performance 为什么';列表和向量不一样吗?(哈斯克尔),performance,haskell,fusion,Performance,Haskell,Fusion,考虑以下基准: module Main where import qualified Data.List as L import qualified Data.Vector.Unboxed as U import Criterion.Main goodSum :: Int -> Double {-# NOINLINE goodSum #-} goodSum n = let ints = U.enumFromN 0 (n * n * 10) :: U.Vector Int i
module Main where
import qualified Data.List as L
import qualified Data.Vector.Unboxed as U
import Criterion.Main
goodSum :: Int -> Double
{-# NOINLINE goodSum #-}
goodSum n =
let ints = U.enumFromN 0 (n * n * 10) :: U.Vector Int
in U.foldl' (+) 0 $ U.map fromIntegral ints
badSum :: Int -> Double
{-# NOINLINE badSum #-}
badSum n = L.foldl' (+) 0.5 [fromIntegral i | i <- [0 .. 10*n*n]]
badSum2 :: Int -> Double
{-# NOINLINE badSum2 #-}
badSum2 n = L.foldr (+) 0.5 [fromIntegral i | i <- [0 .. 10*n*n]]
worstSum :: Int -> Double
{-# NOINLINE worstSum #-}
worstSum n = L.foldl1' (+) $ do
i <- [0 .. n*n]
return $ L.foldl1' (+) $ do
k <- [0 .. 10]
return $ fromIntegral $ k + i
main = do
defaultMain
[ bench "good" $ nf goodSum 500
, bench "bad" $ nf badSum 500
, bench "bad2" $ nf badSum2 500
, bench "worst" $ nf worstSum 500
]
列表理解是,那么为什么列表没有融合呢?与你的问题相反,
foldr
事实上,列表理解是,
保险丝。但是,您必须回忆一下foldr
的定义,而不是它是什么
不是尾部递归函数。前奏曲将foldr
as定义为,它不会
向下编译到一个紧密的循环,如基于向量的示例
foldr k z = go
where
go [] = z
go (y:ys) = y `k` go ys
为badSum2
生成的核心的重要位如下所示
$wgo_s8AH [Occ=LoopBreaker] :: Int# -> Double#
$wgo_s8AH =
\ (w_s8AD :: Int#) ->
case tagToEnum#
@ Bool (==# w_s8AD y1_a69V)
of _ [Occ=Dead] {
False ->
case $wgo_s8AH (+# w_s8AD 1) of ww_s8AG { __DEFAULT ->
+## (int2Double# w_s8AD) ww_s8AG
};
True -> +## (int2Double# w_s8AD) 0.5
这大致相当于此函数(模非固定算术)
通过运行此条件,此函数将提供与相同的运行时
badSum2
。虽然生成的函数没有生成和使用中间cons单元,但它仍在执行函数调用并执行所有相关的堆栈操作
基于foldl'
的版本性能不佳是因为foldl'
不是一个好的消费者,因此它无法与列表理解融合。左折叠将产生一个尾部递归循环,它遍历列表理解生成的列表,导致所有分配和相关内存操作的开销
我不确定您是否可以使用标准列表操作获得与向量
操作相同的性能,但该包提供了使用不同融合方法的列表组合器,这可以在该问题上实现类似的性能
import qualified Data.List.Stream as S
-- The stream-fusion package does not seem to have `enumFrom` functions
enumerate :: Int -> [Int]
enumerate n = S.unfoldr f 0
where
f i | i > n = Nothing
| otherwise = Just (i, i + 1)
goodSum2 :: Int -> Double
goodSum2 n = S.foldl' (+) 0.5 $ S.map fromIntegral $ enumerate (n * n * 10)
与你的问题相反,foldr
和列表理解,事实上,
保险丝。但是,您必须回忆一下foldr
的定义,而不是它是什么
不是尾部递归函数。前奏曲将foldr
as定义为,它不会
向下编译到一个紧密的循环,如基于向量的示例
foldr k z = go
where
go [] = z
go (y:ys) = y `k` go ys
为badSum2
生成的核心的重要位如下所示
$wgo_s8AH [Occ=LoopBreaker] :: Int# -> Double#
$wgo_s8AH =
\ (w_s8AD :: Int#) ->
case tagToEnum#
@ Bool (==# w_s8AD y1_a69V)
of _ [Occ=Dead] {
False ->
case $wgo_s8AH (+# w_s8AD 1) of ww_s8AG { __DEFAULT ->
+## (int2Double# w_s8AD) ww_s8AG
};
True -> +## (int2Double# w_s8AD) 0.5
这大致相当于此函数(模非固定算术)
通过运行此条件,此函数将提供与相同的运行时
badSum2
。虽然生成的函数没有生成和使用中间cons单元,但它仍在执行函数调用并执行所有相关的堆栈操作
基于foldl'
的版本性能不佳是因为foldl'
不是一个好的消费者,因此它无法与列表理解融合。左折叠将产生一个尾部递归循环,它遍历列表理解生成的列表,导致所有分配和相关内存操作的开销
我不确定您是否可以使用标准列表操作获得与向量
操作相同的性能,但该包提供了使用不同融合方法的列表组合器,这可以在该问题上实现类似的性能
import qualified Data.List.Stream as S
-- The stream-fusion package does not seem to have `enumFrom` functions
enumerate :: Int -> [Int]
enumerate n = S.unfoldr f 0
where
f i | i > n = Nothing
| otherwise = Just (i, i + 1)
goodSum2 :: Int -> Double
goodSum2 n = S.foldl' (+) 0.5 $ S.map fromIntegral $ enumerate (n * n * 10)
fyi-Double
有一个Enum
实例,因此map
是不必要的-(\n->enumFromTo 0$fromfomintegral n)::Int->[Double]
rampion:谢谢,但我想确定它正在进行从Int到Double的转换。fyi-Double
有一个Enum
实例,因此,映射是不必要的-(\n->enumFromTo 0$fromIntegral n)::Int->[Double]
rampion:谢谢,但我想确定它正在进行从Int到Double的转换。谢谢,但是foldl'
仍然比矢量版本差15倍。@yong这是因为foldl'
根本不会与生产者融合,因此您仍然需要分配cons单元格来将数据从生产者传输到消费者。谢谢,但是foldl'
仍然比矢量版本差15倍。@yong这是因为foldl'
根本不会与生产者融合,因此您仍然需要分配cons单元格以将数据从生产者传输到消费者。