Por que agrupar a mônada Data.Binary.Put cria um vazamento de memória? (Parte 2)
Como no meupergunta anterior, Estou tentando agrupar a mônada Data.Binary.Put em outra mônada para que mais tarde eu possa fazer perguntas como "quantos bytes ele escreverá" ou "qual é a posição atual no arquivo".
Antes, eu pensava que entender por que ele vaza memória ao usar um invólucro trivial (IdentityT?) Me levaria a resolver meu problema. Mas mesmo que vocês tenham me ajudado a resolver o problema com o invólucro trivial, envolvê-lo com algo útil como StateT ou WriterT ainda consome muita memória (e geralmente trava).
Por exemplo, esta é uma maneira que estou tentando envolvê-lo e que vaza memória para grandes entradas:
type Out = StateT Integer P.PutM () writeToFile :: String -> Out -> IO () writeToFile path out = BL.writeFile path $ P.runPut $ do runStateT out 0 return ()
Aqui é um exemplo de código mais completo que demonstra o problema.
O que eu gostaria de saber é o seguinte:
O que está acontecendo dentro do programa que causa o vazamento de memória?O que posso fazer para corrigi-lo?Para minha segunda pergunta, acho que devo explicar com mais detalhes o que pretendo que os dados visualizem no disco: é basicamente uma estrutura em árvore onde cada nó da árvore é representado como uma tabela de deslocamento para seus filhos (além de alguns dados adicionais). Portanto, para calcular o deslocamento de n-ésimo filho na tabela de deslocamento, preciso conhecer o tamanho dos filhos de 0 a n-1 mais o deslocamento atual (para simplificar, digamos que cada nó tenha um número fixo de filhos).
Obrigado por procurar.
ATUALIZAÇÃO: Graças ao nominolo, agora posso criar uma mônada que envolve o Data.Binary.Put, rastreia o deslocamento atual e quase não usa memória. Isso é feito descartando o uso do transformador StateT em favor de um mecanismo de encadeamento de estado diferente que usa Continuations.
Como isso:
type Offset = Int newtype MyPut a = MyPut { unS :: forall r . (Offset -> a -> P.PutM r) -> Offset -> P.PutM r } instance Monad MyPut where return a = MyPut $ \f s -> f s a ma >>= f = MyPut $ \fb s -> unS ma (\s' a -> unS (f a) fb s') s writeToFile :: String -> MyPut () -> IO () writeToFile path put = BL.writeFile path $ P.runPut $ peal put >> return () where peal myput = unS myput (\o -> return) 0 getCurrentOffset :: MyPut Int getCurrentOffset = MyPut $ \f o -> f o o lift' n ma = MyPut $ \f s -> ma >>= f (s+n)
No entanto, ainda tenho um problema ao rastrear quantos bytes o MyPut vai gravar no disco. Em particular, preciso ter uma função com assinatura como esta:
getSize :: MyPut a -> MyPut Intou
getSize :: MyPut a -> Int
Minha abordagem foi envolver a mônada MyPut dentro do transformador WriterT (algo comoesta) Mas isso começou a consumir muita memória novamente. Como o sclv menciona nos comentários sob a resposta de nominolos, o WriterT de alguma forma cancela o efeito das continuações. Ele também menciona que a obtenção do tamanho deve ser possível diretamente da mônada MyPut que eu já tenho, mas todas as minhas tentativas para fazê-lo terminaram em código não compilável ou em um loop infinito: - |.
Alguém poderia ajudar mais?