Eu estava vagando na Seção Restrita da Biblioteca Haskell e encontrei esses dois feitiços vis:
{- System.IO.Unsafe -}
unsafeDupablePerformIO :: IO a -> a
unsafeDupablePerformIO (IO m) = case runRW# m of (# _, a #) -> a
{- Data.ByteString.Internal -}
accursedUnutterablePerformIO :: IO a -> a
accursedUnutterablePerformIO (IO m) = case m realWorld# of (# _, r #) -> r
A diferença real parece ser apenas entre runRW#
e ($ realWorld#)
, no entanto. Tenho uma idéia básica do que eles estão fazendo, mas não tenho as reais consequências de usar um sobre o outro. Alguém poderia me explicar qual é a diferença?
haskell
io
unsafe
unsafe-perform-io
radrow
fonte
fonte
unsafeDupablePerformIO
é mais seguro por algum motivo. Se eu tivesse que adivinhar, provavelmente tem que fazer algo com inlinear e flutuarrunRW#
. Ansioso para alguém dar uma resposta adequada a esta pergunta.Respostas:
Considere uma biblioteca simplificada de bytestring. Você pode ter um tipo de sequência de bytes composto por um comprimento e um buffer de bytes alocado:
Para criar uma bytestring, você geralmente precisa usar uma ação de E / S:
Porém, não é tão conveniente trabalhar na mônada de IO; portanto, você pode ficar tentado a fazer um IO pouco seguro:
Dada a extensa linha de informações da sua biblioteca, seria interessante incorporar a IO insegura, para obter o melhor desempenho:
Mas, depois de adicionar uma função de conveniência para gerar bytestrings de singleton:
você pode se surpreender ao descobrir que o seguinte programa é impresso
True
:o que é um problema se você espera que dois singletons diferentes usem dois buffers diferentes.
O que está acontecendo de errado aqui é que o inlining extenso significa que as duas
mallocForeignPtrBytes 1
chamadas são iniciadassingleton 1
esingleton 2
podem ser lançadas em uma única alocação, com o ponteiro compartilhado entre as duas sequências de caracteres.Se você remover o inlining de qualquer uma dessas funções, a flutuação será impedida e o programa será impresso
False
conforme o esperado. Como alternativa, você pode fazer a seguinte alteração emmyUnsafePerformIO
:substituindo o
m realWorld#
aplicativo inline por uma chamada de função não inline paramyRunRW# m = m realWorld#
. Esse é o pedaço mínimo de código que, se não for incorporado, pode impedir que as chamadas de alocação sejam levantadas.Após essa alteração, o programa será impresso
False
conforme o esperado.Isso é tudo o que muda de
inlinePerformIO
(AKAaccursedUnutterablePerformIO
) paraunsafeDupablePerformIO
faz.m realWorld#
Altera essa chamada de função de uma expressão embutida para uma equivalente não embutidarunRW# m = m realWorld#
:Exceto que o built-in
runRW#
é mágico. Mesmo marcadoNOINLINE
, ele é realmente incorporado pelo compilador, mas próximo ao final da compilação após as chamadas de alocação já terem sido impedidas de flutuar.Portanto, você obtém o benefício de desempenho de ter a
unsafeDupablePerformIO
chamada totalmente incorporada, sem o efeito colateral indesejável, permitindo que expressões comuns em diferentes chamadas não seguras sejam transferidas para uma única chamada comum.Embora, verdade seja dita, há um custo. Quando
accursedUnutterablePerformIO
funciona corretamente, pode oferecer um desempenho um pouco melhor, pois há mais oportunidades de otimização se am realWorld#
chamada puder ser incorporada mais cedo ou mais tarde. Portanto, abytestring
biblioteca real ainda usaaccursedUnutterablePerformIO
internamente em muitos lugares, principalmente onde não há alocação (por exemplo,head
usa-a para espiar o primeiro byte do buffer).fonte