net*_*tom 5 haskell memory-leaks cpu-usage stream-fusion
这是一个简短的Haskell程序,可产生440 Hz的声音.它使用pulseaudio作为音频后端.
import GHC.Float
import Control.Arrow
import Sound.Pulse.Simple
import qualified Data.List.Stream as S
import Data.List
type Time = Double
type Frequency = Double
type Sample = Double
type CV = Double
chunksize = 441 * 2
sampleRate :: (Fractional a) => a
sampleRate = 44100
integral :: [Double] -> [Double]
integral = scanl1 (\acc x -> acc + x / sampleRate)
chunks :: Int -> [a] -> [[a]]
chunks n = S.takeWhile (not . S.null) . S.unfoldr (Just . S.splitAt n)
pulseaudioOutput :: [Sample] -> IO ()
pulseaudioOutput sx = do
pa <- simpleNew Nothing "Synths" Play Nothing "Synths PCM output"
(SampleSpec (F32 LittleEndian) 44100 1) Nothing Nothing
mapM_ (simpleWrite pa . S.map double2Float) $ chunks 1000 sx
simpleDrain pa
simpleFree pa
oscSine :: Frequency -> [CV] -> [Sample]
oscSine f = S.map sin <<< integral <<< S.map ((2 * pi * f *) . (2**))
music ::[Sample]
music = oscSine 440 (S.repeat 0)
main = do
pulseaudioOutput music
Run Code Online (Sandbox Code Playgroud)
如果我编译并运行它,我会看到不断增长的CPU消耗.
如果我在"chunk"的定义中将"S.splitAt"改为"splitAt",一切都很好.
任何人都可以猜到为什么会这样?
谢谢.
在以下代码中,所有三个版本的块都可以产生上述行为:
import GHC.Float
import Control.Arrow
import Sound.Pulse.Simple
import Data.List.Stream
import Prelude hiding ( unfoldr
, map
, null
, scanl1
, takeWhile
, repeat
, splitAt
, drop
, take
)
type Time = Double
type Frequency = Double
type Sample = Double
type CV = Double
chunksize = 441 * 2
sampleRate :: (Fractional a) => a
sampleRate = 44100
integral :: [Double] -> [Double]
integral = scanl1 (\acc x -> acc + x / sampleRate)
chunks :: Int -> [a] -> [[a]]
--chunks n = takeWhile (not . null) . unfoldr (Just . splitAt n)
--chunks n xs = take n xs : chunks n (drop n xs)
chunks n xs = h : chunks n t
where
(h, t) = splitAt n xs
pulseaudioOutput :: [Sample] -> IO ()
pulseaudioOutput sx = do
pa <- simpleNew Nothing "Synths" Play Nothing "Synths PCM output"
(SampleSpec (F32 LittleEndian) 44100 1) Nothing Nothing
mapM_ (simpleWrite pa . map double2Float) $ chunks 1000 sx
simpleDrain pa
simpleFree pa
oscSine :: Frequency -> [CV] -> [Sample]
oscSine f = map sin <<< integral <<< map ((2 * pi * f *) . (2**))
music ::[Sample]
music = oscSine 440 (repeat 0)
main = do
pulseaudioOutput music
Run Code Online (Sandbox Code Playgroud)
我清理了代码以避免混合普通的旧列表和流融合列表.内存/ CPU泄漏仍然存在.要查看代码是否在旧列表上工作,只需删除Prelude导入和"Data.List"之后的".Stream".
由融合规则( http://hackage.haskell.org/package/stream-fusion-0.1.2.5/docs/Data-Stream.html#g:12splitAt
)替换的流具有以下签名:
splitAt :: Int -> Stream a -> ([a], [a])
Run Code Online (Sandbox Code Playgroud)
由此我们可以看出,由于它产生列表而不是流,这阻碍了进一步的融合。我认为正确的做法是生成splitAt
生成流的 a ,或者更好的是使用chunks
列表版本中的适当融合规则直接在流上编写函数。
这是splitAt
我认为应该不错的直播。当然,您需要将其与splitAt
on 列表中的适当重写规则配对,如果这些重写规则变得棘手,也许可以chunks
直接编写该函数,尽管这样做似乎也有点棘手:
splitAt :: Int -> Stream a -> (Stream a, Stream a)
splitAt n0 (Stream next s0)
| n0 < 0 = (nilStream, (Stream next s0))
| otherwise = loop_splitAt n0 s0
where
nilStream = Stream (const Done) s0
loop_splitAt 0 !s = (nilStream, (Stream next s))
loop_splitAt !n !s = case next s of
Done -> (nilStream, nilStream)
Skip s' -> loop_splitAt n s'
Yield x s' -> (cons x xs', xs'')
where
(xs', xs'') = loop_splitAt (n-1) s'
Run Code Online (Sandbox Code Playgroud)