Optimization 优化Haskell程序

Optimization 优化Haskell程序,optimization,haskell,Optimization,Haskell,我昨天开始研究Haskell,目的是真正学习它。我在编程语言课程中用它编写了一些琐碎的程序,但没有一个真正关心效率。我试图了解如何提高以下程序的运行时间 我的程序解决了以下玩具问题(我知道,如果你知道阶乘是什么,手工计算答案很简单,但我用一个后继函数以蛮力的方式进行计算): 对于给定有限长列表的词典排序的后继函数,我的算法如下: 如果列表已经是降序的,那么我们在字典序中有最大元素,所以没有后继元素 给定一个列表h:t,t在字典序中要么是最大的,要么不是。在后一种情况下,计算t的后继项。在前一种

我昨天开始研究Haskell,目的是真正学习它。我在编程语言课程中用它编写了一些琐碎的程序,但没有一个真正关心效率。我试图了解如何提高以下程序的运行时间

我的程序解决了以下玩具问题(我知道,如果你知道阶乘是什么,手工计算答案很简单,但我用一个后继函数以蛮力的方式进行计算):

对于给定有限长列表的词典排序的后继函数,我的算法如下:

  • 如果列表已经是降序的,那么我们在字典序中有最大元素,所以没有后继元素

  • 给定一个列表h:t,t在字典序中要么是最大的,要么不是。在后一种情况下,计算t的后继项。在前一种情况下,按以下步骤进行

  • 选取t中大于h的最小元素d

  • 将t中的d替换为h,给出新的列表t'。排序中的下一个元素是d:(排序t')

  • 我实现此功能的程序如下所示(其中许多函数可能位于标准库中):

    max\u list::(Ord a)=>a]>a
    max_list[]=错误“空列表没有最大值!”
    最大列表(h:[])=h
    最大列表(h:t)=最大h(最大列表t)
    最小列表::(Ord a)=>[a]->a
    min_list[]=错误“空列表没有最小值!”
    最小列表(h:[])=h
    最小列表(h:t)=最小h(最小列表t)
    --将列表中第一个出现的x替换为y
    替换::(等式a)=>a->a->[a]->[a]
    替换u[]=[]
    更换x y(h:t)
    |h==x=y:t
    |否则=h:(替换x y t)
    --按递增顺序排序
    排序列表::(Ord a)=>[a]->[a]
    排序列表[]=[]
    排序列表(h:t)=(排序列表(过滤器(\x->x>h)t))
    --检查列表是否按降序排列
    降序::(Ord a)=>[a]->Bool
    递减[]=True
    递减(h:[])=真
    下降(h:t)
    |h>(最大列表t)=递减t
    |否则=假
    成功列表::(Ord a)=>[a]->[a]
    成功列表[]=[]
    成功列表(h:[])=[h]
    成功列表(h:t)
    |下降(h:t)=(h:t)
    |不(递减t)=h:成功列表t
    |否则=下一个:排序列表(替换下一个)
    其中next_h=min_列表(过滤器(\x->x>h)t)
    --应用函数n次
    应用次数::(整数n)=>n->(a->a)-->a->a
    应用\u次n\ua
    |n
    这些函数中的很多可能都在标准库中

    的确如此。如果
    导入数据.列表
    ,这使得
    排序
    可用,
    最大值
    最小值
    可从
    序言
    中获得。来自
    Data.List
    排序
    总的来说比准快速排序更有效,特别是因为这里的列表中有很多已排序的块

    descending :: (Ord a) => [a] -> Bool
    descending []     = True
    descending (h:[]) = True
    descending (h:t)
        | h > (max_list t) = descending t
        | otherwise        = False
    
    是低效的-
    O(n²)
    -因为它在每一步中都会遍历整个左尾部,尽管如果列表是递减的,尾部的最大值必须是它的头部但是这有一个很好的结果。由于
    成功列表
    的第三个等式的第一个保护强制对列表进行完全评估,因此它可以防止thunks的累积。但是,如果显式强制列表一次,则可以更有效地执行此操作

    将使其线性化。那

    在注意到我的程序运行了一段时间后,我编写了一个等效的C程序进行比较

    那将是不寻常的。到目前为止,很少有人会在C语言中使用链表,在此基础上实现惰性评估将是一项艰巨的任务

    用C语言编写一个等价的程序将是非常不明智的。在C语言中,实现算法的自然方式是使用数组和就地变异。这在这里会自动地更有效率

    我的猜测是,Haskell的延迟计算导致apply_times函数在实际开始计算结果之前在内存中构建一个巨大的列表

    不完全是,它建造的是一个巨大的撞击

    apply_times 999999 succ_list [0,1,2,3,4,5,6,7,8,9]
    ~> apply_times 999998 succ_list (succ_list [0 .. 9])
    ~> apply_times 999997 succ_list (succ_list (succ_list [0 .. 9]))
    ~> apply_times 999996 succ_list (succ_list (succ_list (succ_list [0 .. 9])))
    ...
    succ_list (succ_list (succ_list ... (succ_list [0 .. 9])...))
    
    而且,在thunk建成之后,必须对其进行评估。要计算最外层调用,必须对下一个调用进行足够的计算,以找出与最外层调用中的模式匹配的模式。因此,将最外层的调用推送到堆栈上,并开始计算下一个调用。为此,必须确定哪种模式匹配,因此需要第三次调用的部分结果。因此,第二个调用被推送到堆栈上。最后,堆栈上有99998个调用,并开始计算最里面的调用。然后在每次调用和下一个外部调用之间打乒乓球(至少,依赖关系可能会传播得更远),同时从堆栈中弹出调用

    有什么好的技巧可以用来最小化内存消耗吗

    是,强制中间列表在成为
    apply\u times
    的参数之前进行计算。这里需要完整的评估,所以普通的
    seq
    不够好

    import Control.DeepSeq
    
    apply_times' :: (NFData a, Integral n) => n -> (a -> a) -> a -> a
    apply_times' 0 _ x = x
    apply_times' k f x = apply_times' (k-1) f $!! f x
    
    这可以防止Thunk的累积,因此您不需要比在
    succ\u list
    中构建的几个短列表和计数器更多的内存

    如何减少复制和垃圾收集,因为列表不断被创建,而C实现可以完成所有工作

    对,这仍然会分配(和垃圾收集)很多。现在,GHC在分配和垃圾收集短期数据方面非常出色(在我的机器上,它可以轻松地以每秒2GB的速度分配数据,而不会太慢),但是,不分配所有这些列表会更快

    所以,如果你想推动它,使用原位突变。工作

    STUArray s Int Int
    
    或者是一个非固定可变向量(我更喜欢
    数组
    包提供的接口,但大多数人更喜欢
    向量
    I)
    import Control.DeepSeq
    
    apply_times' :: (NFData a, Integral n) => n -> (a -> a) -> a -> a
    apply_times' 0 _ x = x
    apply_times' k f x = apply_times' (k-1) f $!! f x
    
    STUArray s Int Int
    
    {-# LANGUAGE BangPatterns #-}
    module Main (main) where
    
    import Data.Array.ST
    import Data.Array.Base
    import Control.Monad.ST
    import Control.Monad (when, replicateM_)
    
    sortPart :: STUArray s Int Int -> Int -> Int -> ST s ()
    sortPart a lo hi
       | lo < hi   = do
           let lscan !p h i
                   | i < h = do
                       v <- unsafeRead a i
                       if p < v then return i else lscan p h (i+1)
                   | otherwise = return i
               rscan !p l i
                   | l < i = do
                       v <- unsafeRead a i
                       if v < p then return i else rscan p l (i-1)
                   | otherwise = return i
               swap i j = do
                   v <- unsafeRead a i
                   unsafeRead a j >>= unsafeWrite a i
                   unsafeWrite a j v
               sloop !p l h
                   | l < h = do
                       l1 <- lscan p h l
                       h1 <- rscan p l1 h
                       if (l1 < h1) then (swap l1 h1 >> sloop p l1 h1) else return l1
                   | otherwise = return l
           piv <- unsafeRead a hi
           i <- sloop piv lo hi
           swap i hi
           sortPart a lo (i-1)
           sortPart a (i+1) hi
       | otherwise = return ()
    
    descending :: STUArray s Int Int -> Int -> Int -> ST s Bool
    descending arr lo hi
        | lo < hi   = do
            let check i !v
                    | hi < i    = return True
                    | otherwise = do
                        w <- unsafeRead arr i
                        if w < v
                          then check (i+1) w
                          else return False
            x <- unsafeRead arr lo
            check (lo+1) x
        | otherwise = return True
    
    findAndReplace :: STUArray s Int Int -> Int -> Int -> ST s ()
    findAndReplace arr lo hi
        | lo < hi   = do
            x <- unsafeRead arr lo
            let go !mi !mv i
                    | hi < i    = when (lo < mi) $ unsafeWrite arr mi x >> unsafeWrite arr lo mv
                    | otherwise = do
                        w <- unsafeRead arr i
                        if x < w && w < mv
                          then go i w (i+1)
                          else go mi mv (i+1)
                look i
                    | hi < i    = return ()
                    | otherwise = do
                        w <- unsafeRead arr i
                        if x < w
                          then go i w (i+1)
                          else look (i+1)
            look (lo+1)
        | otherwise = return ()
    
    succArr :: STUArray s Int Int -> Int -> Int -> ST s ()
    succArr arr lo hi
        | lo < hi   = do
            end <- descending arr lo hi
            if end
              then return ()
              else do
                  needSwap <- descending arr (lo+1) hi
                  if needSwap
                    then do
                        findAndReplace arr lo hi
                        sortPart arr (lo+1) hi
                    else succArr arr (lo+1) hi
        | otherwise = return ()
    
    solution :: [Int]
    solution = runST $ do
        arr <- newListArray (0,9) [0 .. 9]
        replicateM_ 999999 $ succArr arr 0 9
        getElems arr
    
    main :: IO ()
    main = print solution
    
    module Main (main) where
    
    import Data.Array.ST
    import Data.Array.Base
    import Data.Array.Unboxed
    import Control.Monad.ST
    import Control.Monad (when)
    import Data.Bits
    
    lexPerm :: Int -> Int -> [Int]
    lexPerm idx num = elems (runSTUArray $ do
        arr <- unsafeNewArray_ (0,num)
        let fill i
                | num < i   = return ()
                | otherwise = unsafeWrite arr i i >> fill (i+1)
            swap i j = do
                x <- unsafeRead arr i
                y <- unsafeRead arr j
                unsafeWrite arr j x
                unsafeWrite arr i y
            flop i j
                | i < j     = do
                    swap i j
                    flop (i+1) (j-1)
                | otherwise = return ()
            binsearch v a b = go a b
              where
                go i j
                  | i < j     = do
                    let m = (i+j+1) `unsafeShiftR` 1
                    w <- unsafeRead arr m
                    if w < v
                      then go i (m-1)
                      else go m j
                  | otherwise = swap a i
            upstep k j
                | k < 1     = return ()
                | j == num-1 = unsafeRead arr num >>= flip (back k) (num-1)
                | otherwise  = nextP k (num-1)
            back k v i
                | i < 0     = return ()
                | otherwise = do
                    w <- unsafeRead arr i
                    if w < v
                      then nextP k i
                      else back k w (i-1)
            nextP k up
                | k < 1 || up < 0   = return ()
                | otherwise = do
                    v <- unsafeRead arr up
                    binsearch v up num
                    flop (up+1) num
                    upstep (k-1) up
        fill 0
        nextP (idx-1) (num-1)
        return arr)
    
    main :: IO ()
    main = print $ lexPerm 1000000 9