UnsafeDupablePerformIO ve accursedUnutterablePerformIO arasındaki fark nedir?


13

Haskell Kütüphanesi'nin Kısıtlı Bölümünde dolaşıyordum ve şu iki aşağılık büyüyü buldum:

{- 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

Gerçek fark sadece arasına gibi görünüyor runRW#ve ($ realWorld#)ancak. Ne yaptıkları hakkında temel bir fikrim var, ama birini diğerinin üzerinde kullanmanın gerçek sonuçlarını alamıyorum. Birisi bana farkın ne olduğunu açıklayabilir mi?


3
unsafeDupablePerformIOnedense 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.
lehins

Yanıtlar:


11

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 1içinde aramaları singleton 1ve singleton 2iki 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 Falsebeklendiğ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 Falsebeklendiği gibi yazdırılır .

Bu geçiş hepsi inlinePerformIO(AKA accursedUnutterablePerformIO) için unsafeDupablePerformIOyapar. 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, unsafeDupablePerformIOfarklı 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 bytestringkütüphane accursedUnutterablePerformIO, özellikle hiçbir ayırma işleminin olmadığı birçok yerde dahili olarak kullanır (örneğin, headarabelleğin ilk baytını bakmak için kullanır).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.