Je me promenais dans la section restreinte de la bibliothèque Haskell et j'ai trouvé ces deux sorts ignobles:
{- 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
La différence réelle semble toutefois être juste entre runRW#
et ($ realWorld#)
. J'ai une idée de base de ce qu'ils font, mais je n'ai pas les vraies conséquences de les utiliser les uns par rapport aux autres. Quelqu'un pourrait-il m'expliquer quelle est la différence?
haskell
io
unsafe
unsafe-perform-io
radrow
la source
la source
unsafeDupablePerformIO
est plus sûr pour une raison quelconque. Si je devais deviner, il fallait probablement faire quelque chose avec l'incrustation et la flottaisonrunRW#
. Au plaisir que quelqu'un donne une bonne réponse à cette question.Réponses:
Considérons une bibliothèque simplifiée de bytestring. Vous pouvez avoir un type de chaîne d'octets composé d'une longueur et d'un tampon d'octets alloué:
Pour créer un bytestring, vous devez généralement utiliser une action IO:
Ce n'est pas si pratique de travailler dans la monade d'E / S, cependant, vous pourriez être tenté de faire un peu d'E / S non sécurisées:
Compte tenu de l'importante inlining de votre bibliothèque, il serait intéressant d'inclure l'IO dangereux, pour de meilleures performances:
Mais, après avoir ajouté une fonction pratique pour générer des sous-tests singleton:
vous pourriez être surpris de découvrir que le programme suivant s'imprime
True
:ce qui est un problème si vous vous attendez à ce que deux singletons différents utilisent deux tampons différents.
Ce qui ne va pas ici, c'est que l'intensification étendue signifie que les deux
mallocForeignPtrBytes 1
appelssingleton 1
etsingleton 2
peuvent être flottants dans une seule allocation, avec le pointeur partagé entre les deux bytestrings.Si vous deviez supprimer l'inline de l'une de ces fonctions, le flottement serait empêché et le programme s'imprimerait
False
comme prévu. Vous pouvez également apporter la modification suivante àmyUnsafePerformIO
:en remplaçant l'
m realWorld#
application en ligne par un appel de fonction non en ligne àmyRunRW# m = m realWorld#
. Il s'agit de la portion minimale de code qui, si elle n'est pas insérée, peut empêcher la levée des appels d'allocation.Après cette modification, le programme s'imprimera
False
comme prévu.C'est tout ce que le passage de
inlinePerformIO
(AKAaccursedUnutterablePerformIO
) àunsafeDupablePerformIO
fait. Il modifie cet appelm realWorld#
de fonction d'une expression en ligne vers une expression non en ligne équivalenterunRW# m = m realWorld#
:Sauf que le intégré
runRW#
est magique. Même s'il est marquéNOINLINE
, il est en fait inséré par le compilateur, mais vers la fin de la compilation après que les appels d'allocation ont déjà été empêchés de flotter.Ainsi, vous obtenez l'avantage de performances d'avoir l'
unsafeDupablePerformIO
appel entièrement en ligne sans l'effet secondaire indésirable de cette mise en ligne permettant aux expressions communes dans différents appels non sécurisés d'être transférées à un seul appel commun.Mais, à vrai dire, il y a un coût. Lorsqu'il
accursedUnutterablePerformIO
fonctionne correctement, il peut potentiellement donner des performances légèrement meilleures car il y a plus de possibilités d'optimisation si l'm realWorld#
appel peut être intégré plus tôt que tard. Ainsi, labytestring
bibliothèque réelle utilise toujours enaccursedUnutterablePerformIO
interne dans de nombreux endroits, en particulier où il n'y a pas d'allocation en cours (par exemple, l'head
utilise pour jeter un œil au premier octet du tampon).la source