是否有可能使`foldrRanges`和`fol​​drRange2D`一样快?

时间:2015-10-09 21:42:31

标签: algorithm performance haskell

此:

foldrRange :: (Int -> t -> t) -> t -> Int -> Int -> t
foldrRange cons nil a b = foldr cons nil [a..b-1]

定义折叠ab列表的函数。这样:

foldrRange :: (Int -> t -> t) -> t -> Int -> Int -> t
foldrRange cons nil a b = go (b-1) nil where
    go b !r | b < a     = r
            | otherwise = go (b-1) (cons b r)
{-# INLINE foldrRange #-}
由于使用了适当的严格性,

的版本速度提高了约50倍(我们知道最后一个元素,因此我们可以像foldl'一样滚动。)

此:

foldrRange2D cons nil (ax,ay) (bx,by) 
    = foldr cons nil 
    $ do
        y <- [ay..by-1]
        x <- [ax..bx-1]
        return (x,y)

2D的{​​{1}}版本,即它适用于2D矩形,以便foldrRange。这样:

foldrRange2d (:) [] (0,0) (2,2) == [(0,0),(1,0),(0,1),(1,1)]

由于更严格的使用,再次提高了约50倍的定义。写foldrRange2D :: ((Int,Int) -> t -> t) -> t -> (Int,Int) -> (Int,Int) -> t foldrRange2D cons nil (ax,ay) (bx,by) = go (by-1) nil where go by !r | by < ay = r | otherwise = go (by-1) (foldrRange (\ ax -> cons (ax,by)) r ax bx) foldrRange3D等等会很麻烦,所以可以这样概括:

foldrRange4D

不幸的是,这个定义比foldrRangeND :: forall t . ([Int] -> t -> t) -> t -> [Int] -> [Int] -> t foldrRangeND cons nil as bs = foldr co ni (zip as bs) [] nil where co (a,b) tail lis = foldrRange (\ h t -> tail (h:lis) . t) id a b ni lis = cons lis 慢大约120倍,因为可以通过此测试验证:

foldrRange2D

我可以使用main = do let n = 2000 print $ foldrRange2D (\ (a,b) c -> a+b+c) 0 (0,0) (n,n) print $ foldrRanges (\ [a,b] c -> a+b+c) 0 [0,0] [n,n] 来获得更快的ST,但是单独使用递归是否可以这样做?

1 个答案:

答案 0 :(得分:6)

您可以高效地实现算法,该算法在输入的上是归纳的。幸运的是,你可以在Haskell中做到这一点!

首先,用类型级Nat索引向量替换列表。这给了我们一个可以进行归纳的类型(它可能用列表来完成......但这样更安全)。

data Nat = Z | S Nat

infixl 5 :<
data Vec (n :: Nat) (a :: *) where 
  Nil :: Vec Z a 
  (:<) :: Vec n a -> a -> Vec (S n) a 

instance Functor (Vec n) where 
  fmap _ Nil = Nil 
  fmap f (xs :< x) = fmap f xs :< f x

然后你想要的函数和2D情况一样 - 只是推广递归调用:

{-# INLINE foldrRangeN #-}
foldrRangeN :: (Vec n Int -> t -> t) -> t -> Vec n Int -> Vec n Int -> t 
foldrRangeN f x Nil Nil = f Nil x 
foldrRangeN cons nil (ax :< ay) (bx :< by) = go (by-1) nil where
    go by !r 
        | by < ay   = r
        | otherwise = go (by-1) (foldrRangeN (\ ax -> cons (ax :< by)) r ax bx)

虽然当我测试性能时,我很失望地看到它无法跟上2D版本。诀窍似乎更具内线。通过将函数放在一个类中,您可以将它内联到每个“维度”(必须有更好的方法来执行此操作...)

class FoldrRange n where 
  foldrRangeN' :: (Vec n Int -> t -> t) -> t -> Vec n Int -> Vec n Int -> t 

instance FoldrRange Z where
  {-# INLINE foldrRangeN' #-}
  foldrRangeN' f x Nil Nil = f Nil x 

instance FoldrRange n => FoldrRange (S n) where 
  {-# INLINE foldrRangeN' #-}
  foldrRangeN' cons nil (ax :< ay) (bx :< by) = go (by-1) nil where
      go by !r 
          | by < ay   = r
          | otherwise = go (by-1) (foldrRangeN' (\ ax -> cons (ax :< by)) r ax bx)

测试如下:

main = do
  i:n':_ <- getArgs 
  let n = read n' :: Int 
      rs = [ foldrRange2D (\ (a,b) c -> a+b+c) 0 (0,0) (n,n)
           , foldrRangeND (\ [a,b] c -> a+b+c) 0 [0,0] [n,n]
           , foldrRangeN  (\ (Nil :< a :< b) c -> a+b+c) 0 (Nil :< 0 :< 0) (Nil :< n :< n)
           , foldrRangeN' (\ (Nil :< a :< b) c -> a+b+c) 0 (Nil :< 0 :< 0) (Nil :< n :< n)
           ]
  print $ rs !! read i

和我系统上的结果

./test 0 4000 +RTS -s : 0.02s
./test 1 4000 +RTS -s : 7.63s
./test 2 4000 +RTS -s : 0.59s
./test 3 4000 +RTS -s : 0.03s