mapM_ for bytestring
Artyom Kazak
yom at artyom.me
Thu Sep 5 22:38:11 CEST 2013
So, I have written several implementations of mapM_:
* bsMapM_gen — generic, works for any monad
* bsMapM_short — essentially (\f s -> mapM_ f $ unpack s)
* bsMapM_IO — hand-written version specifically for IO
Generic and hand-written versions don’t differ much. The overhead
seems to be coming from inlinePerformIO (am I right here? Also, am
I using inlinePerformIO legitimately?), which is only needed when
we’re not in the IO monad.
{-# SPECIALISE ... IO #-}
{-# SPECIALISE ... ST #-}
bsMapM_gen :: Monad m => (Word8 -> m a) -> ByteString -> m ()
bsMapM_gen f s = unsafePerformIO $ unsafeUseAsCStringLen s mapp
where
mapp (ptr, len) = return $ go 0
where
go i | i == len = return ()
| otherwise = let !b = inlinePerformIO $
peekByteOff ptr i
in f b >> go (i+1)
The short version relies on fusion of `unpack` and `mapM_`. Its
advantage is that even when compiled without optimisations, it’s
still fast. (Question: would the same happen to other versions,
when put into Data.ByteString module? I suppose packages like
bytestring are compiled with optimisations, so it probably would.)
{-# SPECIALISE ... IO #-}
{-# SPECIALISE ... ST #-}
bsMapM_shortIO :: (Word8 -> IO a) -> ByteString -> IO ()
bsMapM_shortIO f s = mapM_ f (unpack s)
Finally, the IO-specialised version. It’s faster than generic
version (and, similarly, an ST-specialised version using
unsafeIOToST would be just as fast), so I assume a SPECIALISE pragma
involving bsMapM_IO and bsMapM_ST should be present.
(Question: are there other monads for which unsafeIOToMonad exists?)
bsMapM_IO :: (Word8 -> IO a) -> ByteString -> IO ()
bsMapM_IO f s = unsafeUseAsCStringLen s mapp
where
mapp (ptr, len) = go 0
where
go i | i == len = return ()
| otherwise = peekByteOff ptr i >>= f >> go (i+1)
A-and here’s a table comparing performance of all three functions.
All timings are in milliseconds.
ghci ghc ghc -O ghc -O2
+----------+----------+----------+----------+
gen | 380 | 85 | 4.1 | 4.0 |
short | 45 | 46 | 17.2 | 16.5 |
IO | 434 | 92 | 2.4 | 2.4 |
+----------+----------+----------+----------+
Here’s the code I used. (Question: have I messed up anything?)
import qualified Data.ByteString as BS
import Data.Random
import System.Random
import System.IO.Unsafe
import Control.Monad
import Data.IORef
import Criterion.Main
import BSMaps
--a bytestring consisting of 65536 random bytes
testCase = BS.pack $ fst $
flip sampleState (mkStdGen 8) $
replicateM (2^16) stdUniform
--sums elements of a bytestring, using given mapM_
sumIO :: ((Word8 -> IO ()) -> BS.ByteString -> IO ()) ->
BS.ByteString -> Word8
sumIO f s = unsafePerformIO $ do
sm <- newIORef 0
f (modifyIORef' sm . (+)) s
readIORef sm
--runs the tests
main = defaultMain [
bench "IO" $ whnf (sumIO bsMapM_IO) testCase,
bench "short" $ whnf (sumIO bsMapM_short) testCase,
bench "gen" $ whnf (sumIO bsMapM_gen) testCase]
Finally, if there isn’t anything wrong, what are my next steps to see
this included into next version of bytestring?
More information about the Libraries
mailing list