Pertimbangkan perpustakaan bytestring yang disederhanakan. Anda mungkin memiliki tipe string byte yang terdiri dari panjang dan buffer byte yang dialokasikan:
data BS = BS !Int !(ForeignPtr Word8)
Untuk membuat bytestring, Anda biasanya perlu menggunakan tindakan IO:
create :: Int -> (Ptr Word8 -> IO ()) -> IO BS
{-# INLINE create #-}
create n f = do
p <- mallocForeignPtrBytes n
withForeignPtr p $ f
return $ BS n p
Namun, tidak mudah bekerja di IO monad, jadi Anda mungkin tergoda untuk melakukan sedikit IO yang tidak aman:
unsafeCreate :: Int -> (Ptr Word8 -> IO ()) -> BS
{-# INLINE unsafeCreate #-}
unsafeCreate n f = myUnsafePerformIO $ create n f
Dengan adanya inlining ekstensif di perpustakaan Anda, alangkah baiknya untuk menyejajarkan IO yang tidak aman, untuk kinerja terbaik:
myUnsafePerformIO :: IO a -> a
{-# INLINE myUnsafePerformIO #-}
myUnsafePerformIO (IO m) = case m realWorld# of (# _, r #) -> r
Tapi, setelah Anda menambahkan fungsi kenyamanan untuk menghasilkan bytestrings singleton:
singleton :: Word8 -> BS
{-# INLINE singleton #-}
singleton x = unsafeCreate 1 (\p -> poke p x)
Anda mungkin terkejut menemukan bahwa program berikut dicetak True
:
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
import GHC.IO
import GHC.Prim
import Foreign
data BS = BS !Int !(ForeignPtr Word8)
create :: Int -> (Ptr Word8 -> IO ()) -> IO BS
{-# INLINE create #-}
create n f = do
p <- mallocForeignPtrBytes n
withForeignPtr p $ f
return $ BS n p
unsafeCreate :: Int -> (Ptr Word8 -> IO ()) -> BS
{-# INLINE unsafeCreate #-}
unsafeCreate n f = myUnsafePerformIO $ create n f
myUnsafePerformIO :: IO a -> a
{-# INLINE myUnsafePerformIO #-}
myUnsafePerformIO (IO m) = case m realWorld# of (# _, r #) -> r
singleton :: Word8 -> BS
{-# INLINE singleton #-}
singleton x = unsafeCreate 1 (\p -> poke p x)
main :: IO ()
main = do
let BS _ p = singleton 1
BS _ q = singleton 2
print $ p == q
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 masuk singleton 1
dan singleton 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 untuk myUnsafePerformIO
:
myUnsafePerformIO :: IO a -> a
{-# INLINE myUnsafePerformIO #-}
myUnsafePerformIO (IO m) = case myRunRW# m of (# _, r #) -> r
myRunRW# :: forall (r :: RuntimeRep) (o :: TYPE r).
(State# RealWorld -> o) -> o
{-# NOINLINE myRunRW# #-}
myRunRW# m = m realWorld#
mengganti m realWorld#
aplikasi inline dengan panggilan fungsi non-inline ke myRunRW# 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
(AKA accursedUnutterablePerformIO
) ke unsafeDupablePerformIO
lakukan. Ini mengubah pemanggilan fungsi m realWorld#
dari ekspresi inline ke non-inline yang setara runRW# m = m realWorld#
:
unsafeDupablePerformIO :: IO a -> a
unsafeDupablePerformIO (IO m) = case runRW# m of (# _, a #) -> a
runRW# :: forall (r :: RuntimeRep) (o :: TYPE r).
(State# RealWorld -> o) -> o
{-# NOINLINE runRW# #-}
runRW# m = m realWorld#
Kecuali, built-in runRW#
adalah sihir. Meskipun itu ditandai NOINLINE
, 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 jika m realWorld#
panggilan dapat diuraikan lebih awal daripada nanti. Jadi, bytestring
perpustakaan aktual masih menggunakan secara accursedUnutterablePerformIO
internal di banyak tempat, khususnya di mana tidak ada alokasi yang terjadi (misalnya, head
menggunakannya untuk mengintip byte pertama dari buffer).
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.