我希望尝试在大量输入上运行一个中等成本的函数,使用该函数的部分输出作为其输入之一。代码按预期运行,不幸的是它在进程中消耗了大量内存(堆上略低于 22GiB,最大驻留略高于 1GiB)。这是我的意思的简化示例:
{-# LANGUAGE OverloadedStrings #-}
import Data.List (foldl')
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.IO as TL
import qualified Data.Text.Lazy.Builder as TB
main :: IO ()
main = TL.putStr $ TB.toLazyText showInts
showInts :: TB.Builder
showInts = foldMap fst shownLines
where
shownLines = map (showInt maxwidth) [0..10^7]
maxwidth = foldl' (\n -> max n . snd) 0 shownLines
showInt :: Int -> Int -> (TB.Builder, Int)
showInt maxwidth n = (builder, len)
where
builder = TB.fromText "This number: "
<> TB.fromText (T.replicate (maxwidth - len) " ") <> thisText
<> TB.singleton '\n'
(thisText, len) = expensiveShow n
expensiveShow :: Int -> (TB.Builder, Int)
expensiveShow n = (TB.fromText text, T.length text)
where text = T.pack (show n)
请注意,在 where 子句中,showInts
作为参数, where本身取决于在整个列表上运行的输出。showInt
maxwidth
maxwidth
showInt maxwidth
另一方面,如果我做一些幼稚的事情并将定义替换为maxwidth
,foldl' max 0 $ map (snd . expensiveShow) [0..10^7]
那么最大驻留量将降至 44KiB。我希望这样的性能可以在没有像预计算这样的变通方法的情况下实现expensiveShow
,然后将它与列表一起压缩[0..10^7]
。
我尝试严格使用列表(使用foldl
包),但这并没有改善情况。
我想吃我的蛋糕并吃掉它:利用懒惰,同时也让事情变得足够严格,以免我们堆积如山。这可能吗?还是有更好的技术来实现这一点?