5

作为更复杂代码的一部分,我正在尝试调试一个性能问题。似乎append我用来创建一个动态的、可增长的向量的(Int,Int,Int,Int)函数导致Int元组中的一个在被写入向量之前被装箱和拆箱。我写了一个更简单的代码来重现这个问题 - 它似乎只有在我在函数中添加矢量增长功能时才会发生append- 下面的示例代码(除了重现问题之外它没有做太多有用的工作),然后是core其中显示的片段装箱和拆箱的值:

{-# LANGUAGE BangPatterns #-}
module Test
where
import Data.Vector.Unboxed.Mutable as MU
import Data.Vector.Unboxed as U hiding (mapM_)
import Control.Monad.ST as ST
import Control.Monad.Primitive (PrimState)
import Control.Monad (when)
import GHC.Float.RealFracMethods (int2Float)
import Data.STRef (newSTRef, writeSTRef, readSTRef)
import Data.Word

type MVI1 s  = MVector (PrimState (ST s)) Int
type MVI4 s  = MVector (PrimState (ST s)) (Int,Int,Int,Int)
data Snakev s = S {-# UNPACK #-}!Int
                                !(MVI4 s)

newVI1 :: Int -> Int -> ST s (MVI1 s)
newVI1 n x = do
          a <- new n
          mapM_ (\i -> MU.unsafeWrite a i x) [0..n-1]
          return a

-- Growable array - we always append an element. It grows by factor of 1.5 if more capacity is needed
append :: Snakev s -> (Int,Int,Int,Int) -> ST s (Snakev s)
append (S i v) x = do
   if i < MU.length v then MU.unsafeWrite v i x >> return (S (i+1) v)
   else MU.unsafeGrow v (floor $! 1.5 * (int2Float $ MU.length v)) >>= (\y -> MU.unsafeWrite y i x >> return (S (i+1) y))

gridWalk :: Vector Word8 -> Vector Word8 -> MVI1 s -> MVI1 s -> Snakev s -> Int -> (Vector Word8 -> Vector Word8 -> Int -> Int ->      Int) -> ST s (Snakev s)
gridWalk a b fp snodes snakesv !k cmp = do
   let offset = 1+U.length a
       xp = offset-k
   snodep <- MU.unsafeRead snodes xp -- get the index of previous snake node in snakev array
   append snakesv (snodep,xp,xp,xp)
{-#INLINABLE gridWalk #-}

GHC 生成一个append用于gridWalk. 该功能$wa在核心 - 请注意装箱的 Int 参数:

$wa
  :: forall s.
     Int#
     -> MVI4 s
     -> Int#
     -> Int#
     -> Int#
     -> Int  ======= Boxed value - one of (Int,Int,Int,Int) is boxed
     -> State# s
     -> (# State# s, Snakev s #)
$wa =
  \ (@ s)
    (ww :: Int#)
    (ww1 :: MVI4 s)
    (ww2 :: Int#)
    (ww3 :: Int#)
    (ww4 :: Int#)
    (ww5 :: Int) === Boxed value
    (w :: State# s) ->

....
....
of ipv12 { __DEFAULT ->
              case (writeIntArray# ipv7 ww ww4 (ipv12 `cast` ...)) `cast` ...
              of ipv13 { __DEFAULT ->
              (# case ww5 of _ { I# x# ->
                 (writeIntArray# ipv10 ww x# (ipv13 `cast` ...)) `cast` ...
                 },
                 S (+# ww 1)
                   ((MV_4
                       (+# y rb)
                       ==== x below unboxed from arg ww5 ======
                       ((MVector 0 x ipv1) `cast` ...) 
                       ((MVector 0 x1 ipv4) `cast` ...)
                       ((MVector 0 x2 ipv7) `cast` ...)
                       ((MVector 0 x3 ipv10) `cast` ...))
                    `cast` ...) #)

gridWalk调用时将值框起来append

=== function called by gridWalk ======
a :: forall s.
     Vector Word8
     -> Vector Word8
     -> MVI1 s
     -> MVI1 s
     -> Snakev s
     -> Int
     -> (Vector Word8 -> Vector Word8 -> Int -> Int -> Int)
     -> State# s
     -> (# State# s, Snakev s #)
a =
  \ (@ s)
    (a1 :: Vector Word8)
    _
    _
    (snodes :: MVI1 s)
    (snakesv :: Snakev s)
    (k :: Int)
    _
    (eta :: State# s) ->
    case k of _ { I# ipv ->
    case snodes `cast` ... of _ { MVector rb _ rb2 ->
    case a1 `cast` ... of _ { Vector _ rb4 _ ->
    let {
      y :: Int#
      y = -# (+# 1 rb4) ipv } in
    case readIntArray# rb2 (+# rb y) (eta `cast` ...)
    of _ { (# ipv1, ipv2 #) ->
    case snakesv of _ { S ww ww1 ->
    ====== y boxed below before append called ======
    $wa ww ww1 ipv2 y y (I# y) (ipv1 `cast` ...) 
    }
    }
    }
    }
    }

因此,效果似乎是在插入 的向量之前将值gridWalk装箱并取消装箱。标记不会改变行为 - 那些装箱的值只是在.append(Int,Int,Int,Int)append INLINEgridWalk

我将欣赏有关如何取消装箱此值的指示。我想append在重构它的同时保留它的功能(即,当容量超过时处理向量增长)。

GHC版本是7.6.1. 矢量版本是0.10.

4

1 回答 1

3

这只是一个评论。我想我会摆脱 tuple 参数(调整appendin的使用gridWalk),但结果是(仅)最后一个 Int 参数必须被 bang'd 让所有东西都拆箱,这看起来很奇怪:

append :: Snakev s -> Int -> Int -> Int -> Int -> ST s (Snakev s)
append (S i v) a b c !d = do
   if i < len then do MU.unsafeWrite v i (a,b,c,d)
                      return $ S (i+1) v
              else do y <- MU.unsafeGrow v additional        
                      MU.unsafeWrite y i (a,b,c,d) 
                      return $ S (i+1) y
  where len = MU.length v           
        additional = floor (1.5 * int2Float len) -- this seems kind of bizarre 
                                        -- by the way; can't you stay inside Int?
                                        --  3 * (len `div` 2) or something

编辑,此外,如果您将应用程序移到S (i+1)do 块之外,您将打开所有内容,但我不确定这是否让我们更接近采石场......:

append :: Snakev s -> Int -> Int -> Int -> Int -> ST s (Snakev s)
append (S i v) a b c d = do
       if i < len then liftM (S (i+1)) $ do MU.unsafeWrite v i (a,b,c,d)
                                            return v
                  else liftM ( S (i+1)) $ do y <- MU.unsafeGrow v zzz         
                                             MU.unsafeWrite y i (a,b,c,d) 
                                             return  y
      where len = MU.length v           
            zzz = floor (1.5 * int2Float len)     

但是如果liftM被替换为,fmap我们又回到了唯一的未装箱中。如果liftM (S (1+i) fmap (S (i+1)被一直移到前面,一切顺利:

append (S i v) a b c d = S (i+1) <$> do ...
于 2013-06-05T22:29:38.040 回答