Saya berkeliaran di Bagian Terbatas dari Perpustakaan Haskell dan menemukan dua mantra keji ini:
{- 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
Perbedaan sebenarnya tampaknya hanya antara runRW#
dan ($ realWorld#)
, bagaimanapun. Saya memiliki beberapa ide dasar tentang apa yang mereka lakukan, tetapi saya tidak mendapatkan konsekuensi nyata dari penggunaan satu sama lain. Bisakah seseorang menjelaskan kepada saya apa bedanya?
haskell
io
unsafe
unsafe-perform-io
Radrow
sumber
sumber
unsafeDupablePerformIO
karena beberapa alasan lebih aman. Jika saya harus menebak itu mungkin harus melakukan sesuatu dengan inlining dan melayang keluarrunRW#
. Menantikan seseorang yang memberikan jawaban yang tepat untuk pertanyaan ini.Jawaban:
Pertimbangkan perpustakaan bytestring yang disederhanakan. Anda mungkin memiliki tipe string byte yang terdiri dari panjang dan buffer byte yang dialokasikan:
Untuk membuat bytestring, Anda biasanya perlu menggunakan tindakan IO:
Namun, tidak mudah bekerja di IO monad, jadi Anda mungkin tergoda untuk melakukan sedikit IO yang tidak aman:
Dengan adanya inlining ekstensif di perpustakaan Anda, alangkah baiknya untuk menyejajarkan IO yang tidak aman, untuk kinerja terbaik:
Tapi, setelah Anda menambahkan fungsi kenyamanan untuk menghasilkan bytestrings singleton:
Anda mungkin terkejut menemukan bahwa program berikut dicetak
True
:yang merupakan masalah jika Anda mengharapkan dua lajang yang berbeda untuk menggunakan dua buffer yang berbeda.
Apa yang salah di sini adalah bahwa inlining yang luas berarti bahwa kedua
mallocForeignPtrBytes 1
panggilan masuksingleton 1
dansingleton 2
dapat dialihkan ke dalam alokasi tunggal, dengan pointer dibagi antara dua bytestrings.Jika Anda menghapus inlining dari salah satu fungsi ini, maka pengapungan akan dicegah, dan program akan mencetak
False
seperti yang diharapkan. Atau, Anda dapat melakukan perubahan berikut untukmyUnsafePerformIO
:mengganti
m realWorld#
aplikasi inline dengan panggilan fungsi non-inline kemyRunRW# m = m realWorld#
. Ini adalah potongan minimal kode yang, jika tidak inline, dapat mencegah panggilan alokasi tidak diangkat.Setelah perubahan ini, program akan mencetak
False
seperti yang diharapkan.Ini semua yang beralih dari
inlinePerformIO
(AKAaccursedUnutterablePerformIO
) keunsafeDupablePerformIO
lakukan. Ini mengubah pemanggilan fungsim realWorld#
dari ekspresi inline ke non-inline yang setararunRW# m = m realWorld#
:Kecuali, built-in
runRW#
adalah sihir. Meskipun itu ditandaiNOINLINE
, itu adalah benar-benar inline oleh compiler, tapi dekat akhir kompilasi setelah panggilan alokasi telah dicegah mengambang.Jadi, Anda mendapatkan manfaat kinerja memiliki
unsafeDupablePerformIO
panggilan yang sepenuhnya diuraikan tanpa efek samping yang tidak diinginkan dari yang memungkinkan ekspresi umum dalam panggilan yang tidak aman yang berbeda untuk dialihkan ke panggilan tunggal yang umum.Padahal, jujur saja, ada biaya. Ketika
accursedUnutterablePerformIO
bekerja dengan benar, ini berpotensi memberikan kinerja yang sedikit lebih baik karena ada lebih banyak peluang untuk optimasi jikam realWorld#
panggilan dapat diuraikan lebih awal daripada nanti. Jadi,bytestring
perpustakaan aktual masih menggunakan secaraaccursedUnutterablePerformIO
internal di banyak tempat, khususnya di mana tidak ada alokasi yang terjadi (misalnya,head
menggunakannya untuk mengintip byte pertama dari buffer).sumber