Basitleştirilmiş bir test kodu kitaplığı düşünün. Bir uzunluk ve ayrılmış bir bayt tamponundan oluşan bir bayt dizesi türünüz olabilir:
data BS = BS !Int !(ForeignPtr Word8)
Bir test testi oluşturmak için genellikle bir ES eylemi kullanmanız gerekir:
create :: Int -> (Ptr Word8 -> IO ()) -> IO BS
{-# INLINE create #-}
create n f = do
p <- mallocForeignPtrBytes n
withForeignPtr p $ f
return $ BS n p
Bununla birlikte, IO monadında çalışmak o kadar uygun değil, bu yüzden biraz güvensiz bir IO yapmaya cazip olabilirsiniz:
unsafeCreate :: Int -> (Ptr Word8 -> IO ()) -> BS
{-# INLINE unsafeCreate #-}
unsafeCreate n f = myUnsafePerformIO $ create n f
Kitaplığınızdaki kapsamlı satır aralığı göz önüne alındığında, en iyi performans için güvenli olmayan IO'yu satır içine almak iyi olurdu:
myUnsafePerformIO :: IO a -> a
{-# INLINE myUnsafePerformIO #-}
myUnsafePerformIO (IO m) = case m realWorld# of (# _, r #) -> r
Ancak, tekli bayt dizeleri oluşturmak için bir kolaylık işlevi ekledikten sonra:
singleton :: Word8 -> BS
{-# INLINE singleton #-}
singleton x = unsafeCreate 1 (\p -> poke p x)
aşağıdaki programın yazdırıldığını görünce şaşırabilirsiniz 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
iki farklı tektonun iki farklı tampon kullanmasını bekliyorsanız bu bir sorundur.
Ne yanlış gidiyor bakalım olduğunu ikiniz o geniş inlining araçlarının mallocForeignPtrBytes 1
içinde aramaları singleton 1
ve singleton 2
iki bytestrings arasında paylaşılan pointer ile, tek tahsisi içine süzülüyor edilebilir.
Satırları bu işlevlerden herhangi birinden çıkarırsanız, kayan önlenir ve program False
beklendiği gibi yazdırılır . Alternatif olarak, şu değişikliği yapabilirsiniz 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#
satır içi m realWorld#
uygulamayı yerine satır içi olmayan bir işlev çağrısı ile değiştirme myRunRW# m = m realWorld#
. Bu, satır içi değilse, ayırma çağrılarının kaldırılmasını önleyebilecek en az kod parçasıdır.
Bu değişiklikten sonra program False
beklendiği gibi yazdırılır .
Bu geçiş hepsi inlinePerformIO
(AKA accursedUnutterablePerformIO
) için unsafeDupablePerformIO
yapar. Bu işlev çağrısını m realWorld#
eğik bir ifadeden eğik olmayan bir eşdeğer olarak değiştirir 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#
Dışında, yerleşik runRW#
sihir. İşaretlendiğini rağmen NOINLINE
, bu edilmektedir aslında derleyici tarafından inlined ancak tahsis aramaların ardından derleme sonuna yakın zaten yüzen engellenmiştir.
Böylece, unsafeDupablePerformIO
farklı güvensiz çağrılardaki ortak ifadelerin ortak bir tek çağrıya dönüştürülmesine izin veren, bu satır aralığının istenmeyen yan etkisi olmadan çağrıyı tamamen satır içine almanın performans avantajını elde edersiniz .
Gerçeği söylemek gerekirse, bir maliyet var. Doğru accursedUnutterablePerformIO
çalıştığında, potansiyel olarak biraz daha iyi performans verebilir, çünkü m realWorld#
çağrı daha sonra değil, daha önce satır içine alınabiliyorsa optimizasyon için daha fazla fırsat vardır . Bu nedenle, gerçek bytestring
kütüphane accursedUnutterablePerformIO
, özellikle hiçbir ayırma işleminin olmadığı birçok yerde dahili olarak kullanır (örneğin, head
arabelleğin ilk baytını bakmak için kullanır).
unsafeDupablePerformIO
nedense daha güvenlidir. Tahmin etmek zorunda kalsaydım, muhtemelen inlining ve kayan bir şey yapmalıydırunRW#
. Bu soruya uygun bir cevap veren birini bekliyorum.