拆箱功能

时间:2013-06-04 20:18:59

标签: performance haskell

我有一个我想要优化的功能。这是一个更大的代码的一部分,我怀疑这个函数阻止GHC在调用它的更高级别函数中取消装箱Int参数。所以,我写了一个简单的测试,考虑到两个方面 - 理解核心,并尝试不同的东西,看看是什么让GHC解包它,以便我可以将课程应用到更大的代码。以下是带有cmp函数包装器的函数test

{-# LANGUAGE BangPatterns #-}
module Cmp
( cmp,
  test )
where
import Data.Vector.Unboxed as U hiding (mapM_)
import Data.Word

cmp :: (U.Unbox a, Eq a) => U.Vector a -> U.Vector a -> Int -> Int -> Int
cmp a b !i !j = go a b 0 i j
               where
                 go v1 v2 !len !i !j| (i<n) && (j<m) && ((unsafeIndex v1 i) == (unsafeIndex v2 j)) = go v1 v2 (len+1) (i+1) (j+1)
                                    | otherwise = len
                   where
                    n = U.length a
                    m = U.length b
{-# INLINABLE cmp #-}

test ::  (U.Unbox a, Eq a) => U.Vector a -> U.Vector a -> U.Vector Int -> Int
test a b i = U.sum $ U.map (\x -> cmp a b x x) i

理想情况下,test应使用以下签名调用cmp的未装箱版本(当然,如果我错了,请更正我):

U.Vector a -> U.Vector a -> Int# -> Int# -> Int#

查看ghc 7.6.1(命令行选项:ghc -fforce-recomp -ddump-simpl -dsuppress-uniques -dsuppress-idinfo -dsuppress-module-prefixes -O2 -fllvm)中生成的核心,我看到这是test的内循环 - 来自下面核心的片段,我添加了评论:< / p>

-- cmp function doesn't have any helper functions with unboxed Int
--
cmp
  :: forall a.
     (Unbox a, Eq a) =>
     Vector a -> Vector a -> Int -> Int -> Int
...

-- This is the function that is called by test - it does keep the result
-- unboxed, but calls boxed cmp, and unboxes the result of cmp (I# y)
--
$wa
  :: forall a.
     (Unbox a, Eq a) =>
     Vector a -> Vector a -> Vector Int -> Int#
$wa =
  \ (@ a)
    (w :: Unbox a)
    (w1 :: Eq a)
    (w2 :: Vector a)
    (w3 :: Vector a)
    (w4 :: Vector Int) ->
    case w4
         `cast` (<TFCo:R:VectorInt> ; <NTCo:R:VectorInt>
                 :: Vector Int ~# Vector Int)
    of _ { Vector ipv ipv1 ipv2 ->
    letrec {
      $s$wfoldlM'_loop :: Int# -> Int# -> Int#
      $s$wfoldlM'_loop =
        \ (sc :: Int#) (sc1 :: Int#) ->
          case >=# sc1 ipv1 of _ {
            False ->
              case indexIntArray# ipv2 (+# ipv sc1) of wild { __DEFAULT ->
              let {
                x :: Int
                x = I# wild } in
              --
              -- Calls cmp and unboxes the Int result as I# y
              --
              case cmp @ a w w1 w2 w3 x x of _ { I# y ->
              $s$wfoldlM'_loop (+# sc y) (+# sc1 1)
              }
              };
            True -> sc
          }; } in
    $s$wfoldlM'_loop 0 0
}

-- helper function called by test - it calls $wa which calls boxed cmp
--
test1
  :: forall a.
     (Unbox a, Eq a) =>
     Vector a -> Vector a -> Vector Int -> Id Int
test1 =
  \ (@ a)
    (w :: Unbox a)
    (w1 :: Eq a)
    (w2 :: Vector a)
    (w3 :: Vector a)
    (w4 :: Vector Int) ->
    case $wa @ a w w1 w2 w3 w4 of ww { __DEFAULT ->
    (I# ww) `cast` (Sym <(NTCo:Id <Int>)> :: Int ~# Id Int)
    }

我将非常感谢如何强制从cmp调用未装箱的test版本。我尝试了不同的论点,但这就像把厨房的水槽扔在上面,这当然不起作用。我希望利用这里学到的经验来解决更复杂的代码中的装箱/拆箱性能问题。

另外,还有一个问题 - 我已经看到cast被用在核心中,但是没有在Haskell / GHC wiki上找到任何核心参考来解释它是什么。这似乎是一种类型的铸造操作。我很感激解释它是什么,以及如何在上面的test1函数中解释它。

1 个答案:

答案 0 :(得分:2)

现在我没有ghc,所以我的建议是口头的: