disk-bytes-0.1.0.0: src/System/Mem/Disk/Bytes.hs
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
-- | Implementation of 'DiskBytes' using GHC-specific primitives
-- like 'mkWeak#'. Unfortunately,
-- I have not been able to bring down the RAM size of 'DiskByte'
-- significantly below 100 bytes yet.
module System.Mem.Disk.Bytes
( DiskBytes
, toDiskBytes
, fromDiskBytes
) where
import Data.ByteString
( ByteString )
import Data.Int
( Int64 )
import System.IO.Unsafe
( unsafePerformIO )
import System.Mem.Disk.DiskApi
( Disk (..) )
import GHC.Exts (Int#, Int(I#))
import GHC.Base
import GHC.Weak
{-----------------------------------------------------------------------------
DiskBytes
------------------------------------------------------------------------------}
-- | A sequence of bytes that is stored on disk
-- — if and only if the value is evaluated to WHNF.
--
-- The value is subject to normal garbage collection:
-- When the value is no longer referenced,
-- the disk memory will be freed (eventually).
--
-- For estimating the memory cost:
-- Even though the bulk of the data is kept on disk,
-- each WHNF of 'DiskBytes' occupies roughly @~100@ bytes of RAM;
-- this is due to administrative overhead like weak pointers and finalizers.
data DiskBytes = DiskBytes
{ index :: Int#
, disk :: Disk
}
-- | Make a weak pointer for our purposes.
addFinalizerBytes :: DiskBytes -> IO () -> IO ()
addFinalizerBytes v@(DiskBytes{}) (IO finalizer) = IO $ \s ->
case mkWeak# v v finalizer s of (# s1, w #) -> (# s1, () #)
-- | Offload a sequence of bytes onto a 'Disk'.
--
-- NOTE: The result must be evaluated to WHNF before the data is actually
-- on disk!
-- Also, the original 'ByteString' needs to be garbage collected
-- in for its RAM to become free.
toDiskBytes :: Disk -> ByteString -> DiskBytes
toDiskBytes disk = unsafePerformIO . mkDiskBytes disk
mkDiskBytes :: Disk -> ByteString -> IO DiskBytes
mkDiskBytes disk bytes = do
I# index <- fromEnum <$> put disk bytes
let diskbytes = DiskBytes{index,disk}
diskbytes `seq` (addFinalizerBytes diskbytes $ finalizerBytes diskbytes)
pure diskbytes
{-# NOINLINE finalizerBytes #-}
finalizerBytes :: DiskBytes -> IO ()
finalizerBytes DiskBytes{index,disk} =
delete disk $ toEnum $ I# index
-- | Read the sequence of bytes back into RAM.
fromDiskBytes :: DiskBytes -> ByteString
fromDiskBytes = unsafePerformIO . unpackM
unpackM :: DiskBytes -> IO ByteString
unpackM DiskBytes{index,disk} =
get disk $ toEnum $ I# index