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单元格以将数据从生产者传输到消费者。