¿Cuál es la diferencia entre unsafeDupablePerformIO y accursedUnutterablePerformIO?

radrow:

Andaba yo en la sección restringida de la biblioteca de Haskell y encontré estos dos hechizos viles:

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

La diferencia real parece ser sólo entre runRW#y ($ realWorld#), sin embargo. Tengo una idea básica de lo que están haciendo, pero no consigo las consecuencias reales de la utilización de uno sobre otro. Podría alguien explicar cuál es la diferencia?

KA Buhr:

Considere una biblioteca ByteString simplificado. Es posible que tenga un tipo de cadena de bytes que consta de una longitud y un búfer asignado de bytes:

data BS = BS !Int !(ForeignPtr Word8)

Para crear una cadena de bytes, por lo general le tenga que utilizar una acción IO:

create :: Int -> (Ptr Word8 -> IO ()) -> IO BS
{-# INLINE create #-}
create n f = do
  p <- mallocForeignPtrBytes n
  withForeignPtr p $ f
  return $ BS n p

No es todo lo conveniente para el trabajo en la mónada IO, aunque, por lo que podría estar tentado a hacer un poco insegura IO:

unsafeCreate :: Int -> (Ptr Word8 -> IO ()) -> BS
{-# INLINE unsafeCreate #-}
unsafeCreate n f = myUnsafePerformIO $ create n f

Dada la extensa procesos en línea en la biblioteca, que sería bueno para el inline insegura IO, para un mejor rendimiento:

myUnsafePerformIO :: IO a -> a
{-# INLINE myUnsafePerformIO #-}
myUnsafePerformIO (IO m) = case m realWorld# of (# _, r #) -> r

Pero, después de agregar una función de conveniencia para la generación de cadenas de bytes Singleton:

singleton :: Word8 -> BS
{-# INLINE singleton #-}
singleton x = unsafeCreate 1 (\p -> poke p x)

puede que se sorprenda al descubrir que los siguientes programas grabados 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

que es un problema si espera dos únicos diferentes de utilizar dos tampones diferentes.

Lo que va mal aquí es que los medios extensos procesos en línea que las dos mallocForeignPtrBytes 1llamadas en singleton 1y singleton 2puede flotar a cabo en una sola asignación, con el puntero compartida entre las dos cadenas de bytes.

Si se va a quitar la expansión en línea de cualquiera de estas funciones, a continuación, se impediría la flotación, y el programa imprimiría Falsecomo se esperaba. Alternativamente, se puede hacer el siguiente cambio 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#

la sustitución de la línea m realWorld#de aplicación con una llamada de función no-inline a myRunRW# m = m realWorld#. Esta es la porción mínima de código que, si no inline, puede impedir que las llamadas de asignación de ser levantado.

Después de este cambio, se imprimirá el programa Falsecomo se esperaba.

Esto es todo lo que el cambio de inlinePerformIO(AKA accursedUnutterablePerformIO) a unsafeDupablePerformIOlo hace. Cambia dicha llamada de función m realWorld#de una expresión inline a un noninlined equivalente 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#

Excepto, el built-in runRW#es magia. A pesar de que éste se crea NOINLINE, que es en realidad inlined por el compilador, pero cerca del final de la compilación después de las llamadas de asignación ya se han impedido flotante.

Así, se obtiene la ventaja de rendimiento de tener la unsafeDupablePerformIOllamada totalmente inline sin el efecto secundario indeseable de que inlining permitiendo expresiones comunes en diferentes llamadas seguras a flotar a una llamada única común.

Aunque, a decir verdad, hay un costo. Cuando accursedUnutterablePerformIOfunciona correctamente, que potencialmente puede dar un rendimiento ligeramente mejor porque hay más oportunidades para la optimización si la m realWorld#llamada puede ser inline más temprano que tarde. Por lo tanto, el actual bytestringbiblioteca todavía utiliza accursedUnutterablePerformIOinternamente en muchos lugares, especialmente donde no hay asignación pasando (por ejemplo, headla usa para mirar el primer byte de la memoria intermedia).

Supongo que te gusta

Origin http://10.200.1.11:23101/article/api/json?id=405884&siteId=1
Recomendado
Clasificación