为什么`Vector.length(Vector.replicate n 0)“没有被融合?

时间:2016-03-18 23:28:28

标签: haskell vector stream-fusion

以下代码意外地(至少对我而言)产生了一个中间向量:

import qualified Data.Vector as Vector

main :: IO ()
main =
  print (test n)

n :: Int
n = 1000000

test :: Int -> Int
test n = Vector.length (Vector.replicate n (0 :: Int))

Core的相关部分在这里(请注意newArray# 1000000调用):

Main.main4
  :: forall s_a38t.
     GHC.Prim.State# s_a38t
     -> (# GHC.Prim.State# s_a38t, Vector.Vector Int #)
[GblId,
 Arity=1,
 Str=DmdType,
 Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
         WorkFree=True, Expandable=True, Guidance=IF_ARGS [0] 399 30}]
Main.main4 =
  \ (@ s_a38t) (s1_a38u [OS=OneShot] :: GHC.Prim.State# s_a38t) ->
    case GHC.Prim.newArray#
           @ Int
           @ (Control.Monad.Primitive.PrimState (GHC.ST.ST s_a38t))
           1000000
           (Data.Vector.Mutable.uninitialised @ Int)
           (s1_a38u
            `cast` ((GHC.Prim.State#
                       (Sym (Control.Monad.Primitive.TFCo:R:PrimStateST[0] <s_a38t>_N)))_R
                    :: GHC.Prim.State# s_a38t
                       ~R# GHC.Prim.State#
                             (Control.Monad.Primitive.PrimState (GHC.ST.ST s_a38t))))
    of _ [Occ=Dead] { (# ipv_a5RG, ipv1_a5RH #) ->
    letrec {
      $wa_s609 [InlPrag=[0], Occ=LoopBreaker]
        :: GHC.Types.SPEC
           -> GHC.Prim.Int#
           -> Bool
           -> GHC.Prim.State# s_a38t
           -> (# GHC.Prim.State# s_a38t, Int #)
      [LclId, Arity=4, Str=DmdType <S,1*U><L,U><S,1*U><L,U>]
      $wa_s609 =
...

同时,如果我将length替换为sum,则会正确进行融合:

test n = Vector.sum (Vector.replicate n (0 :: Int))

核心:

Rec {
Main.main_$s$wfoldlM'_loop [Occ=LoopBreaker]
  :: GHC.Prim.Int# -> GHC.Prim.Int# -> GHC.Prim.Int#
[GblId, Arity=2, Caf=NoCafRefs, Str=DmdType <L,U><L,U>]
Main.main_$s$wfoldlM'_loop =
  \ (sc_s6bx :: GHC.Prim.Int#) (sc1_s6by :: GHC.Prim.Int#) ->
    case GHC.Prim.tagToEnum# @ Bool (GHC.Prim.<=# sc1_s6by 0)
    of _ [Occ=Dead] {
      False ->
        Main.main_$s$wfoldlM'_loop sc_s6bx (GHC.Prim.-# sc1_s6by 1);
      True -> sc_s6bx
    }
end Rec }

Main.main2 :: String
[GblId,
 Str=DmdType,
 Unf=Unf{Src=<vanilla>, TopLvl=True, Value=False, ConLike=False,
         WorkFree=False, Expandable=False, Guidance=IF_ARGS [] 100 30}]
Main.main2 =
  case Main.main_$s$wfoldlM'_loop 0 1000000 of ww_s67W { __DEFAULT ->
  case GHC.Show.$wshowSignedInt 0 ww_s67W (GHC.Types.[] @ Char)
  of _ [Occ=Dead] { (# ww5_a5Vq, ww6_a5Vr #) ->
  GHC.Types.: @ Char ww5_a5Vq ww6_a5Vr
  }
  }

另外,如果我根据monadic流组合器重写原始函数,也不会分配中间向量:

import qualified Data.Vector.Fusion.Stream.Monadic as Stream
import Data.Functor.Identity

test n = runIdentity $ Stream.length (Stream.replicate n (0 :: Int))

核心:

Rec {
Main.main_$s$wfoldlM'_loop [Occ=LoopBreaker]
  :: GHC.Prim.Int# -> GHC.Prim.Int# -> GHC.Prim.Int#
[GblId, Arity=2, Caf=NoCafRefs, Str=DmdType <L,U><L,U>]
Main.main_$s$wfoldlM'_loop =
  \ (sc_s5lE :: GHC.Prim.Int#) (sc1_s5lF :: GHC.Prim.Int#) ->
    case GHC.Prim.tagToEnum# @ Bool (GHC.Prim.<=# sc1_s5lF 0)
    of _ [Occ=Dead] {
      False ->
        Main.main_$s$wfoldlM'_loop
          (GHC.Prim.+# sc_s5lE 1) (GHC.Prim.-# sc1_s5lF 1);
      True -> sc_s5lE
    }
end Rec }

Main.main2 :: String
[GblId,
 Str=DmdType,
 Unf=Unf{Src=<vanilla>, TopLvl=True, Value=False, ConLike=False,
         WorkFree=False, Expandable=False, Guidance=IF_ARGS [] 100 30}]
Main.main2 =
  case Main.main_$s$wfoldlM'_loop 0 1000000 of ww_s5ke { __DEFAULT ->
  case GHC.Show.$wshowSignedInt 0 ww_s5ke (GHC.Types.[] @ Char)
  of _ [Occ=Dead] { (# ww5_a5gi, ww6_a5gj #) ->
  GHC.Types.: @ Char ww5_a5gi ww6_a5gj
  }
  }

为什么Vector.length会破坏融合?

我正在使用ghc-7.10.3vector-0.11.0.0

增加: 这是一个问题:https://github.com/haskell/vector/issues/111

2 个答案:

答案 0 :(得分:4)

我使用了来自sum的{​​{1}}和length而不是Data.Vector.Generic,因为后者只是被定义为前者。

这里是长度代码(来自Data.Vector)...

Data.Vector.Generic

嗯..所以让我们来看看&#34;总和&#34;

-- | /O(1)/ Yield the length of the vector.
length :: Vector v a => v a -> Int
{-# INLINE length #-}
length = Bundle.length . stream

但如果我用-- | /O(n)/ Compute the sum of the elements sum :: (Vector v a, Num a) => v a -> a {-# INLINE sum #-} sum = Bundle.foldl' (+) 0 . stream 运行,我会看到

ghc -ddump-inlinings -ddump-rule-firings -O2

如果我用Rule fired: SPEC Data.Vector.$fVectorVectora [GHC.Types.Int] Inlining done: System.IO.print Inlining done: System.IO.print1 Inlining done: Data.Vector.Generic.sum Rule fired: Class op + Rule fired: Class op fromInteger Inlining done: GHC.Num.$fNumInt_$cfromInteger Rule fired: integerToInt Inlining done: Data.Vector.Fusion.Util.unId Inlining done: Data.Vector.Fusion.Util.unId1 Inlining done: Data.Vector.replicate Inlining done: Data.Vector.Generic.replicate 运行它,我会看到:

length

因此Rule fired: SPEC Data.Vector.$fVectorVectora [GHC.Types.Int] Inlining done: System.IO.print Inlining done: System.IO.print1 Inlining done: Data.Vector.replicate Inlining done: Data.Vector.Generic.replicate Rule fired: SPEC Data.Vector.$fVectorVectora [GHC.Types.Int] 内联而sum没有内容,我不明白为什么。甚至将展开的门槛提高到荒谬的数额并不会改变这一点。

也就是说,如果我手动将length替换为Vector.length,则Bundle.length . Vector.stream规则触发,就像在stream/unstream情况下一样,生成一个非常整洁的核心,没有数组分配。

答案 1 :(得分:2)

这是sclv答案的延伸。

我注意到问题中的行为发生在vector-0.11.0.0,但不是我碰巧安装的其他版本vector-0.10.12.2。通过Data/Vector/Generic.hi检查这两个版本中的ghc --show-iface个文件,我发现仅在版本0.11.0.0中,length(但不是sum)被标记为& #34;环路断路器&#34 ;.这意味着length是相互递归的定义组的一部分,GHC选择此函数不内联,以避免无限扩展的可能性。

我认为发生的事情是0.11.0.0中的更改length定义的一部分,可能是无意的,之前没有,但我没有尝试过验证,因为它需要实际阅读vector源代码。