我有一个我想要优化的功能。这是一个更大的代码的一部分,我怀疑这个函数阻止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
函数中解释它。
答案 0 :(得分:2)
现在我没有ghc
,所以我的建议是口头的:
为什么要避免使用{-# INLINE #-}
pragma? Haskell中的高性能显着基于函数内联。将INLINE
pragma添加到go
函数。
删除go
函数的前两个过多参数。在此处阅读有关内联,专业化(拆箱)参数互操作的更多信息:http://www.haskell.org/ghc/docs/latest/html/users_guide/pragmas.html#inline-pragma
将m
和n
定义与go
一起提升一级。