Я бродил в Запретном разделе Библиотеки Хаскелла и нашел эти два мерзких заклинания:
{- 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
Фактическая разница, кажется, только между runRW#
и ($ realWorld#)
, однако. У меня есть некоторое общее представление о том, что они делают, но я не понимаю реальных последствий использования одного над другим. Может ли кто-нибудь объяснить мне, в чем разница?
haskell
io
unsafe
unsafe-perform-io
radrow
источник
источник
unsafeDupablePerformIO
по какой-то причине безопаснее. Если бы я должен был догадаться, это, вероятно, нужно сделать что-то с использованием inlining и float outrunRW#
. Ждем того, кто даст правильный ответ на этот вопрос.Ответы:
Рассмотрим упрощенную библиотеку тестовых строк. У вас может быть тип строки байтов, состоящий из длины и выделенного буфера байтов:
Чтобы создать строку байтов, вам, как правило, нужно использовать действие ввода-вывода:
Однако работать с монадой ввода-вывода не так уж и удобно, так что у вас может возникнуть соблазн сделать небезопасный ввод-вывод:
Учитывая обширное встраивание в вашу библиотеку, было бы неплохо встроить небезопасный ввод-вывод для лучшей производительности:
Но после того, как вы добавите вспомогательную функцию для генерации одиночных байтовых строк:
Вы можете быть удивлены, обнаружив, что печатает следующая программа
True
:что является проблемой, если вы ожидаете, что два разных синглета будут использовать два разных буфера.
Что здесь не так, так это то, что обширное встраивание означает, что два
mallocForeignPtrBytes 1
вызоваsingleton 1
иsingleton 2
могут быть распределены в одном распределении с указателем, совместно используемым двумя байтовыми строками.Если бы вы удалили вставку из любой из этих функций, то плавание было бы предотвращено, и программа распечатала бы
False
как ожидалось. В качестве альтернативы вы можете внести следующие изменения вmyUnsafePerformIO
:заменив встроенное
m realWorld#
приложение не встроенным вызовом функцииmyRunRW# m = m realWorld#
. Это минимальный кусок кода, который, если он не встроен, может предотвратить отмену вызовов выделения.После этого изменения программа будет печататься,
False
как ожидается.Это все, что переключается с
inlinePerformIO
(AKAaccursedUnutterablePerformIO
) наunsafeDupablePerformIO
. Он изменяет этот вызов функцииm realWorld#
с встроенного выражения на эквивалентное без встроенногоrunRW# m = m realWorld#
:За исключением того, что встроенный
runRW#
является магией. Несмотря на то, что он помеченNOINLINE
, он на самом деле встроен компилятором, но ближе к концу компиляции после того, как вызовы выделения уже были заблокированы.Таким образом, вы получаете выигрыш в производительности от
unsafeDupablePerformIO
полной вставки вызова без нежелательного побочного эффекта от этой вставки, позволяющей распространять общие выражения в различных небезопасных вызовах на общий одиночный вызов.Хотя, по правде говоря, это цена. Когда
accursedUnutterablePerformIO
работает правильно, это может потенциально дать немного лучшую производительность, потому что есть больше возможностей для оптимизации, еслиm realWorld#
вызов может быть встроен раньше, чем позже. Таким образом, настоящаяbytestring
библиотека все еще используетсяaccursedUnutterablePerformIO
внутри во многих местах, в частности, там, где не происходит выделения (например,head
использует ее для просмотра первого байта буфера).источник