packages feed

blaze-builder 0.2.0.3 → 0.2.1.0

raw patch · 27 files changed

+3429/−909 lines, 27 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Blaze.ByteString.Builder: append :: Builder -> Builder -> Builder
- Blaze.ByteString.Builder: empty :: Builder
- Blaze.ByteString.Builder: putWord16be :: Word16 -> Builder
- Blaze.ByteString.Builder: putWord16host :: Word16 -> Builder
- Blaze.ByteString.Builder: putWord16le :: Word16 -> Builder
- Blaze.ByteString.Builder: putWord32be :: Word32 -> Builder
- Blaze.ByteString.Builder: putWord32host :: Word32 -> Builder
- Blaze.ByteString.Builder: putWord32le :: Word32 -> Builder
- Blaze.ByteString.Builder: putWord64be :: Word64 -> Builder
- Blaze.ByteString.Builder: putWord64host :: Word64 -> Builder
- Blaze.ByteString.Builder: putWord64le :: Word64 -> Builder
- Blaze.ByteString.Builder: putWordhost :: Word -> Builder
- Blaze.ByteString.Builder: singleton :: Word8 -> Builder
- Blaze.ByteString.Builder.Internal: BufferFull :: {-# UNPACK #-} !Int -> {-# UNPACK #-} !Ptr Word8 -> !BuildStep -> BuildSignal
- Blaze.ByteString.Builder.Internal: Builder :: (BuildStep -> BuildStep) -> Builder
- Blaze.ByteString.Builder.Internal: Done :: {-# UNPACK #-} !Ptr Word8 -> BuildSignal
- Blaze.ByteString.Builder.Internal: ModifyChunks :: {-# UNPACK #-} !Ptr Word8 -> !ByteString -> ByteString -> !BuildStep -> BuildSignal
- Blaze.ByteString.Builder.Internal: instance Monoid Builder
- Blaze.ByteString.Builder.Internal: newtype Builder
- Blaze.ByteString.Builder.Internal: type BuildStep = Ptr Word8 -> Ptr Word8 -> IO BuildSignal
- Blaze.ByteString.Builder.Write: Write :: {-# UNPACK #-} !Int -> (Ptr Word8 -> IO ()) -> Write
- Blaze.ByteString.Builder.Write: data Write
- Blaze.ByteString.Builder.Write: fromWrite :: Write -> Builder
- Blaze.ByteString.Builder.Write: fromWrite16List :: (a -> Write) -> [a] -> Builder
- Blaze.ByteString.Builder.Write: fromWrite1List :: (a -> Write) -> [a] -> Builder
- Blaze.ByteString.Builder.Write: fromWrite2List :: (a -> Write) -> [a] -> Builder
- Blaze.ByteString.Builder.Write: fromWrite4List :: (a -> Write) -> [a] -> Builder
- Blaze.ByteString.Builder.Write: fromWrite8List :: (a -> Write) -> [a] -> Builder
- Blaze.ByteString.Builder.Write: fromWriteSingleton :: (a -> Write) -> a -> Builder
- Blaze.ByteString.Builder.Write: instance Monoid Write
+ Blaze.ByteString.Builder.Char8: fromChar :: Char -> Builder
+ Blaze.ByteString.Builder.Char8: fromLazyText :: Text -> Builder
+ Blaze.ByteString.Builder.Char8: fromShow :: Show a => a -> Builder
+ Blaze.ByteString.Builder.Char8: fromString :: String -> Builder
+ Blaze.ByteString.Builder.Char8: fromText :: Text -> Builder
+ Blaze.ByteString.Builder.Char8: writeChar :: Char -> Write
+ Blaze.ByteString.Builder.HTTP: chunkedTransferEncoding :: Builder -> Builder
+ Blaze.ByteString.Builder.HTTP: chunkedTransferTerminator :: Builder
+ Blaze.ByteString.Builder.Internal: BufRange :: {-# UNPACK #-} !Ptr Word8 -> {-# UNPACK #-} !Ptr Word8 -> BufRange
+ Blaze.ByteString.Builder.Internal: bufferFull :: Int -> Ptr Word8 -> (BufRange -> IO (BuildSignal a)) -> BuildSignal a
+ Blaze.ByteString.Builder.Internal: data BufRange
+ Blaze.ByteString.Builder.Internal: data BuildStep a
+ Blaze.ByteString.Builder.Internal: data Builder
+ Blaze.ByteString.Builder.Internal: data Put a
+ Blaze.ByteString.Builder.Internal: defaultFirstBufferSize :: Int
+ Blaze.ByteString.Builder.Internal: done :: Ptr Word8 -> a -> BuildSignal a
+ Blaze.ByteString.Builder.Internal: fromBuildStepCont :: (forall r. (BufRange -> IO (BuildSignal r)) -> (BufRange -> IO (BuildSignal r))) -> Builder
+ Blaze.ByteString.Builder.Internal: fromPut :: Put a -> Builder
+ Blaze.ByteString.Builder.Internal: insertByteString :: Ptr Word8 -> ByteString -> (BufRange -> IO (BuildSignal a)) -> BuildSignal a
+ Blaze.ByteString.Builder.Internal: putBuildStepCont :: (forall r. (a -> BufRange -> IO (BuildSignal r)) -> (BufRange -> IO (BuildSignal r))) -> Put a
+ Blaze.ByteString.Builder.Internal: putBuilder :: Builder -> Put ()
+ Blaze.ByteString.Builder.Internal.Buffer: allNewBuffersStrategy :: Int -> BufferAllocStrategy
+ Blaze.ByteString.Builder.Internal.Buffer: allocBuffer :: Int -> IO Buffer
+ Blaze.ByteString.Builder.Internal.Buffer: bufferSize :: Buffer -> Int
+ Blaze.ByteString.Builder.Internal.Buffer: data Buffer
+ Blaze.ByteString.Builder.Internal.Buffer: execBuildStep :: BuildStep a -> Buffer -> IO (BuildSignal a)
+ Blaze.ByteString.Builder.Internal.Buffer: freeSize :: Buffer -> Int
+ Blaze.ByteString.Builder.Internal.Buffer: nextSlice :: Int -> Buffer -> Maybe Buffer
+ Blaze.ByteString.Builder.Internal.Buffer: reuseBuffer :: Buffer -> Buffer
+ Blaze.ByteString.Builder.Internal.Buffer: reuseBufferStrategy :: IO Buffer -> BufferAllocStrategy
+ Blaze.ByteString.Builder.Internal.Buffer: runPut :: Monad m => (IO (BuildSignal a) -> m (BuildSignal a)) -> (Int -> Buffer -> m Buffer) -> (ByteString -> m ()) -> Put a -> Buffer -> m (a, Buffer)
+ Blaze.ByteString.Builder.Internal.Buffer: sliceSize :: Buffer -> Int
+ Blaze.ByteString.Builder.Internal.Buffer: type BufferAllocStrategy = (IO Buffer, Int -> Buffer -> IO (IO Buffer))
+ Blaze.ByteString.Builder.Internal.Buffer: unsafeFreezeBuffer :: Buffer -> ByteString
+ Blaze.ByteString.Builder.Internal.Buffer: unsafeFreezeNonEmptyBuffer :: Buffer -> Maybe ByteString
+ Blaze.ByteString.Builder.Internal.Buffer: updateEndOfSlice :: Buffer -> Ptr Word8 -> Buffer
+ Blaze.ByteString.Builder.Internal.Types: BufRange :: {-# UNPACK #-} !Ptr Word8 -> {-# UNPACK #-} !Ptr Word8 -> BufRange
+ Blaze.ByteString.Builder.Internal.Types: BufferFull :: {-# UNPACK #-} !Int -> {-# UNPACK #-} !Ptr Word8 -> !BuildStep a -> BuildSignal a
+ Blaze.ByteString.Builder.Internal.Types: BuildStep :: (BufRange -> IO (BuildSignal a)) -> BuildStep a
+ Blaze.ByteString.Builder.Internal.Types: Builder :: (forall r. BuildStep r -> BuildStep r) -> Builder
+ Blaze.ByteString.Builder.Internal.Types: Done :: {-# UNPACK #-} !Ptr Word8 -> a -> BuildSignal a
+ Blaze.ByteString.Builder.Internal.Types: InsertByteString :: {-# UNPACK #-} !Ptr Word8 -> !ByteString -> !BuildStep a -> BuildSignal a
+ Blaze.ByteString.Builder.Internal.Types: Put :: (forall r. (a -> BuildStep r) -> BuildStep r) -> Put a
+ Blaze.ByteString.Builder.Internal.Types: bufferFull :: Int -> Ptr Word8 -> (BufRange -> IO (BuildSignal a)) -> BuildSignal a
+ Blaze.ByteString.Builder.Internal.Types: buildStep :: (BufRange -> IO (BuildSignal a)) -> BuildStep a
+ Blaze.ByteString.Builder.Internal.Types: data BufRange
+ Blaze.ByteString.Builder.Internal.Types: data BuildSignal a
+ Blaze.ByteString.Builder.Internal.Types: done :: Ptr Word8 -> a -> BuildSignal a
+ Blaze.ByteString.Builder.Internal.Types: fromBuildStepCont :: (forall r. (BufRange -> IO (BuildSignal r)) -> (BufRange -> IO (BuildSignal r))) -> Builder
+ Blaze.ByteString.Builder.Internal.Types: fromPut :: Put a -> Builder
+ Blaze.ByteString.Builder.Internal.Types: insertByteString :: Ptr Word8 -> ByteString -> (BufRange -> IO (BuildSignal a)) -> BuildSignal a
+ Blaze.ByteString.Builder.Internal.Types: instance Functor Put
+ Blaze.ByteString.Builder.Internal.Types: instance Monad Put
+ Blaze.ByteString.Builder.Internal.Types: instance Monoid Builder
+ Blaze.ByteString.Builder.Internal.Types: newtype BuildStep a
+ Blaze.ByteString.Builder.Internal.Types: newtype Builder
+ Blaze.ByteString.Builder.Internal.Types: newtype Put a
+ Blaze.ByteString.Builder.Internal.Types: putBuildStepCont :: (forall r. (a -> BufRange -> IO (BuildSignal r)) -> (BufRange -> IO (BuildSignal r))) -> Put a
+ Blaze.ByteString.Builder.Internal.Types: putBuilder :: Builder -> Put ()
+ Blaze.ByteString.Builder.Internal.Types: runBuildStep :: BuildStep a -> BufRange -> IO (BuildSignal a)
+ Blaze.ByteString.Builder.Internal.Types: unBuilder :: Builder -> forall r. BuildStep r -> BuildStep r
+ Blaze.ByteString.Builder.Internal.Types: unPut :: Put a -> forall r. (a -> BuildStep r) -> BuildStep r
+ Blaze.ByteString.Builder.Internal.Write: boundedWrite :: Int -> WriteIO -> Write
+ Blaze.ByteString.Builder.Internal.Write: data Write
+ Blaze.ByteString.Builder.Internal.Write: data WriteIO
+ Blaze.ByteString.Builder.Internal.Write: exactWrite :: Int -> (Ptr Word8 -> IO ()) -> Write
+ Blaze.ByteString.Builder.Internal.Write: fromStorable :: Storable a => a -> Builder
+ Blaze.ByteString.Builder.Internal.Write: fromStorables :: Storable a => [a] -> Builder
+ Blaze.ByteString.Builder.Internal.Write: fromWrite :: Write -> Builder
+ Blaze.ByteString.Builder.Internal.Write: fromWriteList :: (a -> Write) -> [a] -> Builder
+ Blaze.ByteString.Builder.Internal.Write: fromWriteSingleton :: (a -> Write) -> a -> Builder
+ Blaze.ByteString.Builder.Internal.Write: instance Monoid Write
+ Blaze.ByteString.Builder.Internal.Write: instance Monoid WriteIO
+ Blaze.ByteString.Builder.Internal.Write: runWrite :: Write -> WriteIO
+ Blaze.ByteString.Builder.Internal.Write: runWriteIO :: WriteIO -> Ptr Word8 -> IO (Ptr Word8)
+ Blaze.ByteString.Builder.Internal.Write: writeN :: Int -> (Ptr Word8 -> IO ()) -> WriteIO
+ Blaze.ByteString.Builder.Internal.Write: writeStorable :: Storable a => a -> Write
- Blaze.ByteString.Builder.Internal: data BuildSignal
+ Blaze.ByteString.Builder.Internal: data BuildSignal a

Files

Blaze/ByteString/Builder.hs view
@@ -62,7 +62,6 @@       Builder        -- * Creating builders-    , module Blaze.ByteString.Builder.Write      , module Blaze.ByteString.Builder.Int     , module Blaze.ByteString.Builder.Word     , module Blaze.ByteString.Builder.ByteString@@ -74,132 +73,11 @@     , toByteString     , toByteStringIO     , toByteStringIOWith-     -      -- * Compatibility to Data.Binary.Builder from the binary package-      ---      -- | The following functions ensure that @"Blaze.ByteString.Builder"@ is a-      -- drop-in replacement for @Data.Binary.Builder@ from the @binary@-      -- package. Note that these functions are deprecated and may be removed-      -- in future versions of the @blaze-builder@ package.-      ---    , empty                   -- DEPRECATED: use 'mempty' instead-    , singleton               -- DEPRECATED: use 'fromByte' instead-    , append                  -- DEPRECATED: use 'mappend' instead-                              -    , putWord16be             -- DEPRECATED: use 'fromWord<n><endian>' instead-    , putWord32be             ---    , putWord64be             ---    , putWord16le             ---    , putWord32le             ---    , putWord64le             --   for all these functions-    , putWordhost             ---    , putWord16host           ---    , putWord32host           ---    , putWord64host           --     ) where  import Blaze.ByteString.Builder.Internal-import Blaze.ByteString.Builder.Write import Blaze.ByteString.Builder.Int import Blaze.ByteString.Builder.Word import Blaze.ByteString.Builder.ByteString--import Data.Monoid-import Data.Word----------------------------------------------------------------------------------- API Compatibility to Data.Binary.Builder from 'binary'----------------------------------------------------------------------------------- | /O(1)/. An empty builder. ------ /Deprecated:/ use 'mempty' instead.-empty :: Builder-empty = mempty-{-# DEPRECATED empty "Use 'mempty' instead." #-}---- | /O(1)/. Append two builders. ------ /Deprecated:/ use 'mappend' instead.-append :: Builder -> Builder -> Builder-append = mappend-{-# DEPRECATED append "Use 'mappend' instead." #-}---- | /O(1)/. Serialize a single byte.------ /Deprecated:/ use 'fromWord8' instead.-singleton :: Word8 -> Builder-singleton = fromWriteSingleton writeWord8-{-# DEPRECATED singleton "Use 'fromWord8' instead." #-}---- | /O(1)/. Serialize a 'Word16' in big endian format.------ /Deprecated:/ use 'fromWord16be' instead.-putWord16be :: Word16 -> Builder-putWord16be = fromWord16be -{-# DEPRECATED putWord16be "Use 'fromWord16be' instead." #-}---- | /O(1)/. Serialize a 'Word32' in big endian format.------ /Deprecated:/ use 'fromWord32be' instead.-putWord32be :: Word32 -> Builder-putWord32be = fromWord32be-{-# DEPRECATED putWord32be "Use 'fromWord32be' instead." #-}---- | /O(1)/. Serialize a 'Word64' in big endian format.------ /Deprecated:/ use 'fromWord64be' instead.-putWord64be :: Word64 -> Builder-putWord64be = fromWord64be-{-# DEPRECATED putWord64be "Use 'fromWord64be' instead." #-}---- | /O(1)/. Serialize a 'Word16' in little endian format.------ /Deprecated:/ use 'fromWord16le' instead.-putWord16le :: Word16 -> Builder-putWord16le = fromWord16le-{-# DEPRECATED putWord16le "Use 'fromWord16le' instead." #-}---- | /O(1)/. Serialize a 'Word32' in little endian format.------ /Deprecated:/ use 'fromWord32le' instead.-putWord32le :: Word32 -> Builder-putWord32le = fromWord32le-{-# DEPRECATED putWord32le "Use 'fromWord32le' instead." #-}---- | /O(1)/. Serialize a 'Word64' in little endian format.------ /Deprecated:/ use 'fromWord64le' instead.-putWord64le :: Word64 -> Builder-putWord64le = fromWord64le-{-# DEPRECATED putWord64le "Use 'fromWord64le' instead." #-}---- | /O(1)/. Serialize a 'Word' in host endian format.------ /Deprecated:/ use 'fromWordhost' instead.-putWordhost :: Word -> Builder-putWordhost = fromWordhost-{-# DEPRECATED putWordhost "Use 'fromWordhost' instead." #-}---- | /O(1)/. Serialize a 'Word16' in host endian format.------ /Deprecated:/ use 'fromWord16host' instead.-putWord16host :: Word16 -> Builder-putWord16host = fromWord16host-{-# DEPRECATED putWord16host "Use 'fromWord16host' instead." #-}---- | /O(1)/. Serialize a 'Word32' in host endian format.------ /Deprecated:/ use 'fromWord32host' instead.-putWord32host :: Word32 -> Builder-putWord32host = fromWord32host-{-# DEPRECATED putWord32host "Use 'fromWord32host' instead." #-}---- | /O(1)/. Serialize a 'Word64' in host endian format.------ /Deprecated:/ use 'fromWord64host' instead.-putWord64host :: Word64 -> Builder-putWord64host = fromWord64host-{-# DEPRECATED putWord64host "Use 'fromWord64host' instead." #-} 
Blaze/ByteString/Builder/ByteString.hs view
@@ -34,8 +34,8 @@      ) where -import Blaze.ByteString.Builder.Write-import Blaze.ByteString.Builder.Internal+import           Blaze.ByteString.Builder.Internal hiding (insertByteString)+import qualified Blaze.ByteString.Builder.Internal as I   (insertByteString)  import Foreign import Data.Monoid@@ -59,7 +59,7 @@ -- | Write a strict 'S.ByteString' to a buffer. -- writeByteString :: S.ByteString -> Write-writeByteString bs = Write l io+writeByteString bs = exactWrite l io   where   (fptr, o, l) = S.toForeignPtr bs   io pf = withForeignPtr fptr $ \p -> copyBytes pf (p `plusPtr` o) l@@ -97,22 +97,14 @@ fromByteStringWith :: Int          -- ^ Maximal number of bytes to copy.                    -> S.ByteString -- ^ Strict 'S.ByteString' to serialize.                    -> Builder      -- ^ Resulting 'Builder'.-fromByteStringWith maximalCopySize bs = Builder step+fromByteStringWith maxCopySize = +    \bs -> fromBuildStepCont $ step bs   where-    step k pf pe-      | maximalCopySize < size  = -          return $ ModifyChunks pf (L.Chunk bs) k-      | pf `plusPtr` size <= pe = do-          withForeignPtr fpbuf $ \pbuf -> -              copyBytes pf (pbuf `plusPtr` offset) size-          let pf' = pf `plusPtr` size-          pf' `seq` k pf' pe-      | otherwise               = return $ BufferFull size pf (step k)-      where-        (fpbuf, offset, size) = S.toForeignPtr bs+    step !bs !k br@(BufRange !op _)+      | maxCopySize < S.length bs = return $ I.insertByteString op bs k+      | otherwise                 = copyByteStringStep bs k br {-# INLINE fromByteStringWith #-} - -- | @copyByteString bs@ serialize the strict bytestring @bs@ by copying it to -- the output buffer.  --@@ -120,9 +112,31 @@ -- to be smallish (@<= 4kb@). -- copyByteString :: S.ByteString -> Builder-copyByteString = fromWriteSingleton writeByteString+copyByteString = \bs -> fromBuildStepCont $ copyByteStringStep bs {-# INLINE copyByteString #-} +copyByteStringStep :: S.ByteString +                   -> (BufRange -> IO (BuildSignal a))+                   -> (BufRange -> IO (BuildSignal a))+copyByteStringStep (S.PS ifp ioff isize) !k = +    goBS (unsafeForeignPtrToPtr ifp `plusPtr` ioff)+  where+    !ipe = unsafeForeignPtrToPtr ifp `plusPtr` (ioff + isize)+    goBS !ip !(BufRange op ope)+      | inpRemaining <= outRemaining = do+          copyBytes op ip inpRemaining+          touchForeignPtr ifp -- input consumed: OK to release from here+          let !br' = BufRange (op `plusPtr` inpRemaining) ope+          k br'+      | otherwise = do+          copyBytes op ip outRemaining+          let !ip' = ip `plusPtr` outRemaining+          return $ bufferFull 1 ope (goBS ip')+      where+        outRemaining = ope `minusPtr` op+        inpRemaining = ipe `minusPtr` ip+{-# INLINE copyByteStringStep #-}+ -- | @insertByteString bs@ serializes the strict bytestring @bs@ by inserting -- it directly as a chunk of the output stream.  --@@ -132,8 +146,10 @@ -- to be processed efficiently. -- insertByteString :: S.ByteString -> Builder-insertByteString bs = Builder $ \ k pf _ ->-    return $ ModifyChunks pf (L.Chunk bs) k+insertByteString = +    \bs -> fromBuildStepCont $ step bs+  where+    step !bs !k !(BufRange op _) = return $ I.insertByteString op bs k {-# INLINE insertByteString #-}  @@ -172,31 +188,8 @@ fromLazyByteStringWith :: Int          -- ^ Maximal number of bytes to copy.                        -> L.ByteString -- ^ Lazy 'L.ByteString' to serialize.                        -> Builder      -- ^ Resulting 'Builder'.-fromLazyByteStringWith maximalCopySize = -    makeBuilder-  where-    -- FIXME: Justify this first case split. Can we justify it at all? I seem-    -- to remember that its idea was to enable a partial inlining; i.e. sharing-    -- of 'step' between different calls to 'fromLazyByteStringWith'.-    makeBuilder L.Empty  = mempty-    makeBuilder lbs0     = Builder $ step lbs0-      where-        step lbs1 k pf0 pe0 = go lbs1 pf0-          where-            go L.Empty                !pf = k pf pe0-            go lbs@(L.Chunk bs' lbs') !pf-              | maximalCopySize < size = -                  return $ ModifyChunks pf (L.Chunk bs') (step lbs' k)--              | pf' <= pe0 = do-                  withForeignPtr fpbuf $ \pbuf -> -                      copyBytes pf (pbuf `plusPtr` offset) size-                  go lbs' pf'--              | otherwise  = return $ BufferFull size pf (step lbs k)-              where-                pf' = pf `plusPtr` size-                (fpbuf, offset, size) = S.toForeignPtr bs'+fromLazyByteStringWith maxCopySize = +  L.foldrChunks (\bs b -> fromByteStringWith maxCopySize bs `mappend` b) mempty {-# INLINE fromLazyByteStringWith #-}  @@ -205,10 +198,9 @@ -- -- See 'copyByteString' for usage considerations. ------ FIXME: Implement fused L.toChunks and fromWrite1List copyLazyByteString :: L.ByteString -> Builder-copyLazyByteString = fromWrite1List writeByteString . L.toChunks+copyLazyByteString = +  L.foldrChunks (\bs b -> copyByteString bs `mappend` b) mempty {-# INLINE copyLazyByteString #-}  -- | /O(n)/. Serialize a lazy bytestring by inserting /all/ its chunks directly@@ -220,10 +212,7 @@ -- need an /O(1)/ lazy bytestring insert based on difference lists. -- insertLazyByteString :: L.ByteString -> Builder-insertLazyByteString lbs = Builder step-  where-    step k pf _ = -        return $ ModifyChunks pf (\lbs' -> L.foldrChunks L.Chunk lbs' lbs) k-+insertLazyByteString =+  L.foldrChunks (\bs b -> insertByteString bs `mappend` b) mempty {-# INLINE insertLazyByteString #-} 
Blaze/ByteString/Builder/Char/Utf8.hs view
@@ -35,45 +35,26 @@ import qualified Data.Text.Lazy.Encoding as TS -- imported for documentation links  import Blaze.ByteString.Builder.Internal-import Blaze.ByteString.Builder.Write  -- | Write a UTF-8 encoded Unicode character to a buffer. ----- Note that the control flow of 'writeChar' is more complicated than the one--- of 'writeWord8', as the size of the write depends on the 'Char' written.--- Therefore,------ > fromWrite $ writeChar a `mappend` writeChar b------ must not always be faster than------ > fromChar a `mappend` fromChar b------ Use benchmarking to make informed decisions.------- FIXME: Use a Write that always checks if 4 bytes are available and only take--- care of the precise pointer advance once the data has been written. Either--- formulate it using continuation passing or returning the increment using the--- IO action. The latter is probably simpler and better understandable.---+{-# INLINE writeChar #-} writeChar :: Char -> Write-writeChar = encodeCharUtf8 f1 f2 f3 f4+writeChar c = boundedWrite 4 (encodeCharUtf8 f1 f2 f3 f4 c)   where-    f1 x = Write 1 $ \ptr -> poke ptr x--    f2 x1 x2 = Write 2 $ \ptr -> do poke ptr x1-                                    poke (ptr `plusPtr` 1) x2+    f1 x1          = writeN 1 $ \op -> do pokeByteOff op 0 x1 -    f3 x1 x2 x3 = Write 3 $ \ptr -> do poke ptr x1-                                       poke (ptr `plusPtr` 1) x2-                                       poke (ptr `plusPtr` 2) x3+    f2 x1 x2       = writeN 2 $ \op -> do pokeByteOff op 0 x1+                                          pokeByteOff op 1 x2+                   +    f3 x1 x2 x3    = writeN 3 $ \op -> do pokeByteOff op 0 x1+                                          pokeByteOff op 1 x2+                                          pokeByteOff op 2 x3 -    f4 x1 x2 x3 x4 = Write 4 $ \ptr -> do poke ptr x1-                                          poke (ptr `plusPtr` 1) x2-                                          poke (ptr `plusPtr` 2) x3-                                          poke (ptr `plusPtr` 3) x4-{-# INLINE writeChar #-}+    f4 x1 x2 x3 x4 = writeN 4 $ \op -> do pokeByteOff op 0 x1+                                          pokeByteOff op 1 x2+                                          pokeByteOff op 2 x3+                                          pokeByteOff op 3 x4  -- | Encode a Unicode character to another datatype, using UTF-8. This function -- acts as an abstract way of encoding characters, as it is unaware of what@@ -113,7 +94,7 @@ -- | /O(n)/. Serialize a Unicode 'String' using the UTF-8 encoding. -- fromString :: String -> Builder-fromString = fromWrite1List writeChar+fromString = fromWriteList writeChar -- Performance note: ^^^ -- --   fromWrite2List made things slightly worse for the blaze-html benchmarks
+ Blaze/ByteString/Builder/Char8.hs view
@@ -0,0 +1,78 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} +-- ignore warning from 'import Data.Text.Encoding'++-- |+-- Module      : Blaze.ByteString.Builder.Char8+-- Copyright   : (c) 2010 Simon Meier+-- License     : BSD3-style (see LICENSE)+-- +-- Maintainer  : Simon Meier <iridcode@gmail.com>+-- Stability   : experimental+-- Portability : tested on GHC only+--+-- //Note:// This package is intended for low-level use like implementing+-- protocols. If you need to //serialize// Unicode characters use one of the +-- UTF encodings (e.g. 'Blaze.ByteString.Builder.Char.UTF-8').+--+-- 'Write's and 'Builder's for serializing the lower 8-bits of characters.+--+-- This corresponds to what the 'bytestring' package offer in+-- 'Data.ByteString.Char8'.+--+module Blaze.ByteString.Builder.Char8+    ( +      -- * Writing Latin-1 (ISO 8859-1) encodable characters to a buffer+      writeChar++      -- * Creating Builders from Latin-1 (ISO 8859-1) encodable characters+    , fromChar+    , fromString+    , fromShow+    , fromText+    , fromLazyText+    ) where++import Foreign+import Data.Char (ord)++import qualified Data.Text               as TS+import qualified Data.Text.Encoding      as TS -- imported for documentation links+import qualified Data.Text.Lazy          as TL+import qualified Data.Text.Lazy.Encoding as TS -- imported for documentation links++import Blaze.ByteString.Builder.Internal+import Blaze.ByteString.Builder.Word++-- | Write the lower 8-bits of a character to a buffer.+--+{-# INLINE writeChar #-}+writeChar :: Char -> Write+writeChar = writeWord8 . fromIntegral . ord++-- | /O(1)/. Serialize the lower 8-bits of a character.+--+fromChar :: Char -> Builder+fromChar = fromWriteSingleton writeChar++-- | /O(n)/. Serialize the lower 8-bits of all characters of a string+--+fromString :: String -> Builder+fromString = fromWriteList writeChar++-- | /O(n)/. Serialize a value by 'Show'ing it and serializing the lower 8-bits+-- of the resulting string.+--+fromShow :: Show a => a -> Builder+fromShow = fromString . show++-- | /O(n)/. Serialize the lower 8-bits of all characters in the strict text.+--+{-# INLINE fromText #-}+fromText :: TS.Text -> Builder+fromText = fromString . TS.unpack++-- | /O(n)/. Serialize the lower 8-bits of all characters in the lazy text.+--+{-# INLINE fromLazyText #-}+fromLazyText :: TL.Text -> Builder+fromLazyText = fromString . TL.unpack
+ Blaze/ByteString/Builder/HTTP.hs view
@@ -0,0 +1,210 @@+{-# LANGUAGE BangPatterns, CPP, MagicHash, OverloadedStrings #-}+-- | Support for HTTP response encoding.+--+-- TODO: Cleanup!+module Blaze.ByteString.Builder.HTTP (+  -- * Chunked HTTP transfer encoding+    chunkedTransferEncoding+  , chunkedTransferTerminator+  ) where++import Data.Monoid+import qualified Data.ByteString as S++import Foreign++import Blaze.ByteString.Builder.Internal+import Blaze.ByteString.Builder.Internal.Types+import Blaze.ByteString.Builder.Internal.UncheckedShifts++import qualified Blaze.ByteString.Builder.Char8 as Char8++-- only required by test-code+-- import qualified Data.ByteString.Lazy as L+-- import qualified Blaze.ByteString.Builder.ByteString  as B+-- import Data.ByteString.Char8 ()+++-- | Write a CRLF sequence.+writeCRLF :: Write+writeCRLF = Char8.writeChar '\r' `mappend` Char8.writeChar '\n'+{-# INLINE writeCRLF #-}++-- | Execute a write+{-# INLINE execWrite #-}+execWrite :: Write -> Ptr Word8 -> IO ()+execWrite w op = do+    _ <- runWriteIO (runWrite w) op+    return ()+++------------------------------------------------------------------------------+-- Hex Encoding Infrastructure+------------------------------------------------------------------------------++{-+pokeWord16Hex :: Word16 -> Ptr Word8 -> IO ()+pokeWord16Hex x op = do+    pokeNibble 0 12+    pokeNibble 1  8+    pokeNibble 2  4+    pokeNibble 3  0+  where+    pokeNibble off s+        | n <  10   = pokeWord8 off (fromIntegral $ 48 + n)+        | otherwise = pokeWord8 off (fromIntegral $ 55 + n)+        where+          n = shiftr_w16 x s .&. 0xF++    pokeWord8 :: Int -> Word8 -> IO ()+    pokeWord8 off = poke (op `plusPtr` off)++writeWord16Hex :: Word16 -> Write+writeWord16Hex = exactWrite 4 . pokeWord16Hex++-}++pokeWord32HexN :: Int -> Word32 -> Ptr Word8 -> IO ()+pokeWord32HexN n0 w0 op0 = +    go w0 (op0 `plusPtr` (n0 - 1))+  where+    go !w !op+      | op < op0  = return ()+      | otherwise = do+          let nibble :: Word8+              nibble = fromIntegral w .&. 0xF+              hex | nibble < 10 = 48 + nibble+                  | otherwise   = 55 + nibble+          poke op hex+          go (w `shiftr_w32` 4) (op `plusPtr` (-1))+{-# INLINE pokeWord32HexN #-}++iterationsUntilZero :: Integral a => (a -> a) -> a -> Int+iterationsUntilZero f = go 0+  where+    go !count 0  = count+    go !count !x = go (count+1) (f x)+{-# INLINE iterationsUntilZero #-}++-- | Length of the hex-string required to encode the given 'Word32'.+word32HexLength :: Word32 -> Int+word32HexLength = max 1 . iterationsUntilZero (`shiftr_w32` 4)+{-# INLINE word32HexLength #-}++writeWord32Hex :: Word32 -> Write+writeWord32Hex w = +    boundedWrite (2 * sizeOf w) (writeN len $ pokeWord32HexN len w)+  where+    len = word32HexLength w+{-# INLINE writeWord32Hex #-}++{-+test = flip (toLazyByteStringWith 32 32 32) L.empty+    $ chunkedTransferEncoding+    $ mconcat $ map oneLine [0..16] +++                [B.insertByteString "hello"] +++                map oneLine [0,1] +++                [B.insertByteString ""] +++                map oneLine [0..16]+               +  where+    oneLine x = fromWriteSingleton writeWord32Hex x `mappend` Char8.fromChar ' '+-}++------------------------------------------------------------------------------+-- Chunked transfer encoding+------------------------------------------------------------------------------++-- | Transform a builder such that it uses chunked HTTP transfer encoding.+chunkedTransferEncoding :: Builder -> Builder+chunkedTransferEncoding (Builder b) =+    fromBuildStepCont transferEncodingStep+  where+    transferEncodingStep k = go (b (buildStep k))+      where+        go :: BuildStep a -> BufRange -> IO (BuildSignal a)+        go innerStep !(BufRange op ope)+          -- FIXME: Assert that outRemaining < maxBound :: Word32+          | outRemaining < minimalBufferSize = +              return $ bufferFull minimalBufferSize op (go innerStep)+          | otherwise = do+              let !brInner@(BufRange opInner _) = BufRange +                     (op  `plusPtr` (chunkSizeLength + 2))     -- leave space for chunk header+                     (ope `plusPtr` (-maxAfterBufferOverhead)) -- leave space at end of data++                  -- wraps the chunk, if it is non-empty, and returns the+                  -- signal constructed with the correct end-of-data pointer+                  {-# INLINE wrapChunk #-}+                  wrapChunk :: Ptr Word8 -> (Ptr Word8 -> IO (BuildSignal a)) +                            -> IO (BuildSignal a)+                  wrapChunk !opInner' mkSignal +                    | opInner' == opInner = mkSignal op+                    | otherwise           = do+                        pokeWord32HexN chunkSizeLength +                            (fromIntegral $ opInner' `minusPtr` opInner)+                            op+                        execWrite writeCRLF (opInner `plusPtr` (-2))+                        execWrite writeCRLF opInner'+                        mkSignal (opInner' `plusPtr` 2)++              -- execute inner builder with reduced boundaries+              signal <- runBuildStep innerStep brInner+              case signal of+                Done opInner' x ->+                    wrapChunk opInner' $ \op' -> +                      return $! done op' x++                BufferFull minRequiredSize opInner' nextInnerStep ->+                    wrapChunk opInner' $ \op' ->+                      return $! bufferFull +                        (minRequiredSize + maxEncodingOverhead) +                        op'+                        (go nextInnerStep)  ++                InsertByteString opInner' bs nextInnerStep +                  | S.null bs ->                        -- flush+                      wrapChunk opInner' $ \op' ->+                        return $! insertByteString +                          op' S.empty +                          (go nextInnerStep)++                  | otherwise ->                        -- insert non-empty bytestring+                      wrapChunk opInner' $ \op' -> do+                        -- add header for inserted bytestring+                        -- FIXME: assert(S.length bs < maxBound :: Word32)+                        !op'' <- (`runWriteIO` op') $ runWrite $+                            writeWord32Hex (fromIntegral $ S.length bs) +                            `mappend` writeCRLF+                        -- insert bytestring and write CRLF in next buildstep+                        return $! InsertByteString+                          op'' bs+                          (unBuilder (fromWrite writeCRLF) $ +                            buildStep $ go nextInnerStep)+                  +          where+            -- minimal size guaranteed for actual data no need to require more+            -- than 1 byte to guarantee progress the larger sizes will be+            -- hopefully provided by the driver or requested by the wrapped+            -- builders.+            minimalChunkSize  = 1   +                                    +            -- overhead computation+            maxBeforeBufferOverhead = sizeOf (undefined :: Int) + 2 -- max chunk size and CRLF after header+            maxAfterBufferOverhead  = 2 +                           -- CRLF after data+                                      sizeOf (undefined :: Int) + 2 -- max bytestring size, CRLF after header++            maxEncodingOverhead = maxBeforeBufferOverhead + maxAfterBufferOverhead++            minimalBufferSize = minimalChunkSize + maxEncodingOverhead++            -- remaining and required space computation+            outRemaining :: Int+            outRemaining    = ope `minusPtr` op+            chunkSizeLength = word32HexLength $ fromIntegral outRemaining+      ++-- | The '0\r\n' chunk header signaling the termination of the data transfer.+chunkedTransferTerminator :: Builder+chunkedTransferTerminator = +  fromWrite $ Char8.writeChar '0' `mappend` writeCRLF+
Blaze/ByteString/Builder/Html/Utf8.hs view
@@ -36,17 +36,22 @@ import qualified Data.Text.Lazy as TL  import Blaze.ByteString.Builder+import Blaze.ByteString.Builder.Internal import Blaze.ByteString.Builder.Char.Utf8  -- | Write a HTML escaped and UTF-8 encoded Unicode character to a bufffer. -- writeHtmlEscapedChar :: Char -> Write-writeHtmlEscapedChar '<'  = writeByteString "&lt;"-writeHtmlEscapedChar '>'  = writeByteString "&gt;"-writeHtmlEscapedChar '&'  = writeByteString "&amp;"-writeHtmlEscapedChar '"'  = writeByteString "&quot;"-writeHtmlEscapedChar '\'' = writeByteString "&#39;"-writeHtmlEscapedChar c    = writeChar c+writeHtmlEscapedChar c0 = +    boundedWrite 6 (io c0)+    -- WARNING: Don't forget to change the bound if you change the bytestrings.+  where+    io '<'  = runWrite $ writeByteString "&lt;"+    io '>'  = runWrite $ writeByteString "&gt;"+    io '&'  = runWrite $ writeByteString "&amp;"+    io '"'  = runWrite $ writeByteString "&quot;"+    io '\'' = runWrite $ writeByteString "&#39;"+    io c    = runWrite $ writeChar c {-# INLINE writeHtmlEscapedChar #-}  -- | /O(1)./ Serialize a HTML escaped Unicode character using the UTF-8@@ -59,7 +64,7 @@ -- encoding. -- fromHtmlEscapedString :: String -> Builder-fromHtmlEscapedString = fromWrite1List writeHtmlEscapedChar+fromHtmlEscapedString = fromWriteList writeHtmlEscapedChar  -- | /O(n)/. Serialize a value by 'Show'ing it and then, HTML escaping and -- UTF-8 encoding the resulting 'String'.
Blaze/ByteString/Builder/Int.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE CPP #-} -- | -- Module      : Blaze.ByteString.Builder.Int -- Copyright   : (c) 2010 Simon Meier@@ -14,10 +13,6 @@ -- See "Blaze.ByteString.Builder.Word" for information about how to best write several -- integers at once. ---#if defined(__GLASGOW_HASKELL__) && !defined(__HADDOCK__)-#include "MachDeps.h"-#endif- module Blaze.ByteString.Builder.Int     (      -- * Writing integers to a buffer@@ -45,8 +40,7 @@     -- | We provide serialization functions both for singleton integers as well as     -- for lists of integers. Using these list serialization functions is /much/ faster     -- than using @mconcat . map fromInt/<n/>@, as the list serialization-    -- functions use a tighter inner loop and a chunked write like it is-    -- provided by functions such as 'fromWrite2List'.+    -- functions use a tighter inner loop.      , fromInt8     , fromInt8s@@ -80,7 +74,6 @@     ) where  import Blaze.ByteString.Builder.Internal-import Blaze.ByteString.Builder.Write import Blaze.ByteString.Builder.Word  import Foreign@@ -142,26 +135,22 @@ -- conversion. -- writeInthost :: Int -> Write-writeInthost w = -    Write (sizeOf (undefined :: Int)) (\p -> poke (castPtr p) w)+writeInthost = writeStorable {-# INLINE writeInthost #-}  -- | Write an 'Int16' in native host order and host endianness. writeInt16host :: Int16 -> Write-writeInt16host w16 = -    Write (sizeOf (undefined :: Int16)) (\p -> poke (castPtr p) w16)+writeInt16host = writeStorable {-# INLINE writeInt16host #-}  -- | Write an 'Int32' in native host order and host endianness. writeInt32host :: Int32 -> Write-writeInt32host w32 = -    Write (sizeOf (undefined :: Int32)) (\p -> poke (castPtr p) w32)+writeInt32host = writeStorable {-# INLINE writeInt32host #-}  -- | Write an 'Int64' in native host order and host endianness. writeInt64host :: Int64 -> Write-writeInt64host w = -    Write (sizeOf (undefined :: Int64)) (\p -> poke (castPtr p) w)+writeInt64host = writeStorable {-# INLINE writeInt64host #-}  @@ -180,7 +169,7 @@ -- | Serialize a list of bytes. -- fromInt8s :: [Int8] -> Builder-fromInt8s = fromWrite8List writeInt8+fromInt8s = fromWriteList writeInt8   -- Int16@@ -193,7 +182,7 @@  -- | Serialize a list of 'Int16's in big endian format. fromInt16sbe :: [Int16] -> Builder-fromInt16sbe = fromWrite4List writeInt16be +fromInt16sbe = fromWriteList writeInt16be  {-# INLINE fromInt16sbe #-}  -- | Serialize an 'Int16' in little endian format.@@ -203,7 +192,7 @@  -- | Serialize a list of 'Int16's in little endian format. fromInt16sle :: [Int16] -> Builder-fromInt16sle = fromWrite4List writeInt16le +fromInt16sle = fromWriteList writeInt16le  {-# INLINE fromInt16sle #-}  @@ -217,7 +206,7 @@  -- | Serialize a list of 'Int32's in big endian format. fromInt32sbe :: [Int32] -> Builder-fromInt32sbe = fromWrite2List writeInt32be +fromInt32sbe = fromWriteList writeInt32be  {-# INLINE fromInt32sbe #-}  -- | Serialize an 'Int32' in little endian format.@@ -227,7 +216,7 @@  -- | Serialize a list of 'Int32's in little endian format. fromInt32sle :: [Int32] -> Builder-fromInt32sle = fromWrite2List writeInt32le +fromInt32sle = fromWriteList writeInt32le  {-# INLINE fromInt32sle #-}  -- | Serialize an 'Int64' in big endian format.@@ -237,7 +226,7 @@  -- | Serialize a list of 'Int64's in big endian format. fromInt64sbe :: [Int64] -> Builder-fromInt64sbe = fromWrite1List writeInt64be +fromInt64sbe = fromWriteList writeInt64be  {-# INLINE fromInt64sbe #-}  -- | Serialize an 'Int64' in little endian format.@@ -247,7 +236,7 @@  -- | Serialize a list of 'Int64's in little endian format. fromInt64sle :: [Int64] -> Builder-fromInt64sle = fromWrite1List writeInt64le +fromInt64sle = fromWriteList writeInt64le  {-# INLINE fromInt64sle #-}  @@ -267,7 +256,7 @@ -- | Serialize a list of 'Int's. -- See 'fromInthost' for usage considerations. fromIntshost :: [Int] -> Builder-fromIntshost = fromWrite2List writeInthost +fromIntshost = fromWriteList writeInthost  {-# INLINE fromIntshost #-}  -- | Write an 'Int16' in native host order and host endianness.@@ -277,7 +266,7 @@  -- | Write a list of 'Int16's in native host order and host endianness. fromInt16shost :: [Int16] -> Builder-fromInt16shost = fromWrite4List writeInt16host +fromInt16shost = fromWriteList writeInt16host  {-# INLINE fromInt16shost #-}  -- | Write an 'Int32' in native host order and host endianness.@@ -287,7 +276,7 @@  -- | Write a list of 'Int32's in native host order and host endianness. fromInt32shost :: [Int32] -> Builder-fromInt32shost = fromWrite2List writeInt32host +fromInt32shost = fromWriteList writeInt32host  {-# INLINE fromInt32shost #-}  -- | Write an 'Int64' in native host order and host endianness.@@ -297,5 +286,5 @@  -- | Write a list of 'Int64's in native host order and host endianness. fromInt64shost :: [Int64] -> Builder-fromInt64shost = fromWrite1List writeInt64host+fromInt64shost = fromWriteList writeInt64host {-# INLINE fromInt64shost #-}
Blaze/ByteString/Builder/Internal.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE CPP, BangPatterns #-}+{-# LANGUAGE CPP, BangPatterns, Rank2Types #-} -- | -- Module      : Blaze.ByteString.Builder.Internal -- Copyright   : (c) 2010 Simon Meier@@ -8,158 +8,57 @@ -- Stability   : experimental -- Portability : tested on GHC only ----- Implementation of the 'Builder' monoid.------ A standard library user must never import this module directly. Instead, he--- should import "Blaze.ByteString.Builder", which re-exports the 'Builder' type and--- its associated public functions defined in this module.------ Developers of other libraries may import this module to gain access to the--- internal representation of builders. For example, in some cases, creating a--- 'Builder' with a custom low-level 'BuildStep' may improve performance--- considerably compared to the creating it using the public 'Builder'--- combinators (e.g., @'fromWrite1List'@ in "Blaze.ByteString.Builder.Write").--- Another example, is the use of 'ModifyChunks' to efficiently wire the--- 'Builder' type with another library that generates lazy bytestrings.------ In any case, whenever you import this module you /must/ reference the full--- version of the 'blaze-builder' package in your cabal file, as the--- implementation and the guarantees given in this file may change in any--- version! The release notes will tell, if this was the case.+-- Core types and functions for the 'Builder' monoid and the 'Put' monad. ---module Blaze.ByteString.Builder.Internal-    ( -    -- * The @Builder@ type-      Builder(..)-    , BuildStep-    , BuildSignal(..)--    -- * Flushing the buffer-    , flush--    -- * Executing builders-    , toLazyByteStringWith-    , toLazyByteString-    , toByteString-    , toByteStringIOWith-    , toByteStringIO+module Blaze.ByteString.Builder.Internal ( -    -- * Default sizes-    , defaultBufferSize-    , defaultMinimalBufferSize-    , defaultMaximalCopySize-    ) where+  -- * Build Steps+    BufRange(..)+  , BuildSignal+  , BuildStep+  , done+  , bufferFull+  , insertByteString +  -- * Builder+  , Builder+  , fromBuildStepCont+  , fromPut+  , flush -import Foreign-import Data.Monoid-import Control.Monad (unless)-import qualified Data.ByteString      as S-import qualified Data.ByteString.Lazy as L+  -- * Put+  , Put+  , putBuilder+  , putBuildStepCont -#ifdef BYTESTRING_IN_BASE-import Data.ByteString.Base (inlinePerformIO)-import qualified Data.ByteString.Base as S-import qualified Data.ByteString.Lazy.Base as L -- FIXME: is this the right module for access to 'Chunks'?-#else-import Data.ByteString.Internal (inlinePerformIO)-import qualified Data.ByteString.Internal as S-import qualified Data.ByteString.Lazy.Internal as L-#endif+  -- * Writes+  , module Blaze.ByteString.Builder.Internal.Write +  -- * Execution+  , toLazyByteString+  , toLazyByteStringWith+  , toByteString+  , toByteStringIO+  , toByteStringIOWith ---------------------------------------------------------------------------------- The Builder type-------------------------------------------------------------------------------+  -- * Deafult Sizes+  , defaultFirstBufferSize+  , defaultMinimalBufferSize+  , defaultBufferSize+  , defaultMaximalCopySize +) where --- | Intuitively, a builder denotes the construction of a lazy bytestring. ------ Builders can be created from primitive buffer manipulations using the--- @'Write'@ abstraction provided by in "Blaze.ByteString.Builder.Write". However for--- many Haskell values, there exist predefined functions doing that already. --- For example, UTF-8 encoding 'Char' and 'String' values is provided by the--- functions in "Blaze.ByteString.Builder.Char.Utf8". Concatenating builders is done--- using their 'Monoid' instance.------ Semantically, builders are nothing special. They just denote a sequence of--- bytes. However, their representation is chosen such that this sequence of--- bytes can be efficiently (in terms of CPU cycles) computed in an--- incremental, chunk-wise fashion such that the average chunk-size is large.--- Note that the large average chunk size allows to make good use of cache--- prefetching in later processing steps (e.g. compression) or to reduce the--- sytem call overhead when writing the resulting lazy bytestring to a file or--- sending it over the network.------ For precisely understanding the performance of a specific 'Builder',--- benchmarking is unavoidable. Moreover, it also helps to understand the--- implementation of builders and the predefined combinators. This should be--- amenable to the average Haskell programmer by reading the source code of--- "Blaze.ByteString.Builder.Internal" and the other modules of this library. ------ The guiding implementation principle was to reduce the abstraction cost per--- output byte. We use continuation passing to achieve a constant time append.--- The output buffer is filled by the individual builders as long as possible.--- They call each other directly when they are done and control is returned to--- the driver (e.g., 'toLazyByteString') only when the buffer is full, a--- bytestring needs to be inserted directly, or no more bytes can be written.--- We also try to take the pressure off the cache by moving variables as far--- out of loops as possible. This leads to some duplication of code, but--- results in sometimes dramatic increases in performance. For example, see the--- @'fromWord8s'@ function in "Blaze.ByteString.Builder.Word".----newtype Builder = Builder (BuildStep -> BuildStep)+import Foreign --- | A 'BuildSignal' signals to the driver of the 'Builder' execution the next--- step to be taken.----data BuildSignal =-  -- | @Done pf@ signals that the 'BuildStep' is finished and data has been-  -- written up to the next free byte @pf@.-    Done {-# UNPACK #-} !(Ptr Word8) -  -- | @BufferFull newSize pf nextStep@ signals that the buffer is full and-  -- data has been written up to the next free byte @pf@. Moreover, the next-  -- build step to be executed @nextStep@ requires a buffer of at least size-  -- @newSize@ to execute successfully.-  ---  -- A driver /must/ guarantee that the buffer used to call @nextStep@ is at-  -- least of size @newSize@.-  | BufferFull-      {-# UNPACK #-} !Int-      {-# UNPACK #-} !(Ptr Word8)-                     !BuildStep-  -- | @ModifyChunks pf fbs nextStep@ signals that the data written up to the-  -- next free byte @pf@ must be output and the remaining lazy bytestring that-  -- is produced by executing @nextStep@ must be modified using the function-  -- @fbs@.-  ---  -- This signal is used to insert bytestrings directly into the output stream.-  -- It can also be used to efficiently hand over control to another library-  -- for generating streams of strict bytestrings.-  | ModifyChunks-      {-# UNPACK #-} !(Ptr Word8) -                     !(L.ByteString -> L.ByteString) -                     !BuildStep+import Control.Monad (unless) --- | A 'BuildStep' fills a buffer from the given start pointer as long as--- possible and returns control to the caller using a 'BuildSignal', once it is--- required.----type BuildStep =  Ptr Word8       -- ^ Pointer to the next free byte in the-                                  -- buffer. A 'BuildStep' must start writing-                                  -- its data from this address.-               -> Ptr Word8       -- ^ Pointer to the first byte /after/ the-                                  -- buffer.  A 'BuildStep' must never write-                                  -- data at or after this address.-               -> IO BuildSignal  -- ^ Signal to the driver about the next step-                                  -- to be taken.+import qualified Data.ByteString               as S+import qualified Data.ByteString.Internal      as S+import qualified Data.ByteString.Lazy          as L+import qualified Data.ByteString.Lazy.Internal as L -instance Monoid Builder where-    mempty = Builder id-    {-# INLINE mempty #-}-    mappend (Builder f) (Builder g) = Builder $ f . g-    {-# INLINE mappend #-}-    mconcat = foldr mappend mempty-    {-# INLINE mconcat #-}+import Blaze.ByteString.Builder.Internal.Types+import Blaze.ByteString.Builder.Internal.Write  ------------------------------------------------------------------------------ -- Internal global constants.@@ -202,6 +101,12 @@ ------------------------------------------------------------------------------ -- Flushing and running a Builder ------------------------------------------------------------------------------+                    +-- | Prepend the chunk if it is non-empty.+{-# INLINE nonEmptyChunk #-}+nonEmptyChunk :: S.ByteString -> L.ByteString -> L.ByteString+nonEmptyChunk bs lbs | S.null bs = lbs +                     | otherwise = L.Chunk bs lbs   -- | Output all data written in the current buffer and start a new chunk.@@ -216,8 +121,11 @@ -- 'L.ByteString'. The remaining part of the buffer is spilled, if the -- reamining free space is smaller than the minimal desired buffer size. --+{-# INLINE flush #-} flush :: Builder-flush = Builder $ \k pf _ -> return $ ModifyChunks pf id k+flush = fromBuildStepCont step+  where+    step k !(BufRange op _) = return $ insertByteString op S.empty k  -- | Run a 'Builder' with the given buffer sizes. --@@ -258,9 +166,9 @@                      -- finished.     -> L.ByteString  -- ^ Resulting lazy bytestring toLazyByteStringWith bufSize minBufSize firstBufSize (Builder b) k = -    inlinePerformIO $ fillFirstBuffer (b finalStep)+    S.inlinePerformIO $ fillFirstBuffer (b (buildStep finalStep))   where-    finalStep pf _ = return $ Done pf+    finalStep (BufRange pf _) = return $ Done pf ()     -- fill a first very small buffer, if we need more space then copy it     -- to the new buffer of size 'minBufSize'. This way we don't pay the     -- allocation cost of the big 'bufSize' buffer, when outputting only@@ -273,25 +181,26 @@               let !pe      = pf `plusPtr` firstBufSize                   mkbs pf' = S.PS fpbuf 0 (pf' `minusPtr` pf)                   {-# INLINE mkbs #-}-              next <- step0 pf pe+              next <- runBuildStep step0 (BufRange pf pe)               case next of-                  Done pf' +                  Done pf' _                     | pf' == pf -> return k                     | otherwise -> return $ L.Chunk (mkbs pf') k                    BufferFull newSize pf' nextStep  -> do                       let !l  = pf' `minusPtr` pf-                      fillNewBuffer (max (l + newSize) minBufSize) $-                          \pfNew peNew -> do+                      fillNewBuffer (max (l + newSize) minBufSize) $ buildStep $+                          \(BufRange pfNew peNew) -> do                               copyBytes pfNew pf l-                              nextStep (pfNew `plusPtr` l) peNew+                              let !br' = BufRange (pfNew `plusPtr` l) peNew+                              runBuildStep nextStep br'                       -                  ModifyChunks pf' bsk nextStep +                  InsertByteString pf' bs nextStep                        | pf' == pf ->-                          return $ bsk (inlinePerformIO $ fillNewBuffer bufSize nextStep)+                          return $ nonEmptyChunk bs (S.inlinePerformIO $ fillNewBuffer bufSize nextStep)                       | otherwise ->                           return $ L.Chunk (mkbs pf')-                              (bsk (inlinePerformIO $ fillNewBuffer bufSize nextStep))+                              (nonEmptyChunk bs (S.inlinePerformIO $ fillNewBuffer bufSize nextStep))                          -- allocate and fill a new buffer     fillNewBuffer !size !step0 = do@@ -302,28 +211,31 @@           where             !pe = pbuf `plusPtr` size             fill !pf !step = do-                next <- step pf pe+                next <- runBuildStep step (BufRange pf pe)                 let mkbs pf' = S.PS fpbuf (pf `minusPtr` pbuf) (pf' `minusPtr` pf)                     {-# INLINE mkbs #-}                 case next of-                    Done pf' +                    Done pf' _                       | pf' == pf -> return k                       | otherwise -> return $ L.Chunk (mkbs pf') k -                    BufferFull newSize pf' nextStep ->-                        return $ L.Chunk (mkbs pf')-                            (inlinePerformIO $ -                                fillNewBuffer (max newSize bufSize) nextStep)+                    BufferFull newSize pf' nextStep +                      | pf' == pf -> +                          fillNewBuffer (max newSize bufSize) nextStep+                      | otherwise -> +                          return $ L.Chunk (mkbs pf')+                              (S.inlinePerformIO $ +                                  fillNewBuffer (max newSize bufSize) nextStep)                         -                    ModifyChunks  pf' bsk nextStep+                    InsertByteString  pf' bs nextStep                       | pf' == pf                      ->-                          return $ bsk (inlinePerformIO $ fill pf' nextStep)+                          return $ nonEmptyChunk bs (S.inlinePerformIO $ fill pf' nextStep)                       | minBufSize < pe `minusPtr` pf' ->                           return $ L.Chunk (mkbs pf')-                              (bsk (inlinePerformIO $ fill pf' nextStep))+                              (nonEmptyChunk bs (S.inlinePerformIO $ fill pf' nextStep))                       | otherwise                      ->                           return $ L.Chunk (mkbs pf')-                              (bsk (inlinePerformIO $ fillNewBuffer bufSize nextStep))+                              (nonEmptyChunk bs (S.inlinePerformIO $ fillNewBuffer bufSize nextStep))   -- | Extract the lazy 'L.ByteString' from the builder by running it with default@@ -395,35 +307,30 @@                    -> Builder                  -- ^ 'Builder' to run.                    -> IO ()                    -- ^ Resulting 'IO' action. toByteStringIOWith bufSize io (Builder b) = -    fillNewBuffer bufSize (b finalStep)+    fillBuffer bufSize (b (buildStep finalStep))   where-    finalStep pf _ = return $ Done pf+    finalStep !(BufRange pf _) = return $ Done pf () -    fillNewBuffer !size !step0 = do-        S.mallocByteString size >>= fillBuffer+    fillBuffer !size step = do+        S.mallocByteString size >>= fill       where-        fillBuffer fpbuf = fill step0-          where-            -- safe because the constructed ByteString references the foreign-            -- pointer AFTER its buffer was filled.-            pf = unsafeForeignPtrToPtr fpbuf-            fill !step = do-                next <- step pf (pf `plusPtr` size)-                case next of-                    Done pf' ->-                        unless (pf' == pf) (io $  S.PS fpbuf 0 (pf' `minusPtr` pf))+        fill fpbuf = do+            let !pf = unsafeForeignPtrToPtr fpbuf+                !br = BufRange pf (pf `plusPtr` size)+                -- safe due to later reference of fpbuf+                -- BETTER than withForeignPtr, as we lose a tail call otherwise+            signal <- runBuildStep step br+            case signal of+                Done pf' _ -> io $ S.PS fpbuf 0 (pf' `minusPtr` pf) -                    BufferFull newSize pf' nextStep  -> do-                        io $ S.PS fpbuf 0 (pf' `minusPtr` pf)-                        if bufSize < newSize-                          then fillNewBuffer newSize nextStep-                          else fill nextStep-                        -                    ModifyChunks  pf' bsk nextStep  -> do-                        unless (pf' == pf) (io $  S.PS fpbuf 0 (pf' `minusPtr` pf))-                        -- was: mapM_ io $ L.toChunks (bsk L.empty)-                        L.foldrChunks (\bs -> (io bs >>)) (return ()) (bsk L.empty)-                        fill nextStep+                BufferFull minSize pf' nextStep  -> do+                    io $ S.PS fpbuf 0 (pf' `minusPtr` pf)+                    fillBuffer (max bufSize minSize) nextStep+                    +                InsertByteString pf' bs nextStep  -> do+                    io $ S.PS fpbuf 0 (pf' `minusPtr` pf)+                    unless (S.null bs) (io bs)+                    fillBuffer bufSize nextStep  -- | Run the builder with a 'defaultBufferSize'd buffer and execute the given -- 'IO' action whenever the buffer is full or gets flushed.@@ -439,3 +346,66 @@ toByteStringIO = toByteStringIOWith defaultBufferSize {-# INLINE toByteStringIO #-} ++------------------------------------------------------------------------------+-- Draft of new builder/put execution code+------------------------------------------------------------------------------++{- FIXME: Generalize this code such that it can replace the above clunky+ - implementations.+              +-- | A monad for lazily composing lazy bytestrings using continuations.+newtype LBSM a = LBSM { unLBSM :: (a, L.ByteString -> L.ByteString) }++instance Monad LBSM where+    return x                       = LBSM (x, id)+    (LBSM (x,k)) >>= f             = let LBSM (x',k') = f x in LBSM (x', k . k')+    (LBSM (_,k)) >> (LBSM (x',k')) = LBSM (x', k . k')++-- | Execute a put and return the written buffers as the chunks of a lazy+-- bytestring.+toLazyByteString :: Put a -> (a, L.ByteString)+toLazyByteString put = +    (fst result, k (bufToLBSCont (snd result) L.empty))+  where++    -- FIXME: Check with ByteString guys why allocation in inlinePerformIO is+    -- bad.++    -- initial buffer+    buf0 = S.inlinePerformIO $ allocBuffer defaultBufferSize+    -- run put, but don't force result => we're lazy enough+    LBSM (result, k) = runPut liftIO outputBuf outputBS put buf0+    -- convert a buffer to a lazy bytestring continuation+    bufToLBSCont = maybe id L.Chunk . unsafeFreezeNonEmptyBuffer+    -- lifting an io putsignal to a lazy bytestring monad+    liftIO io = LBSM (S.inlinePerformIO io, id)+    -- add buffer as a chunk prepare allocation of new one+    outputBuf minSize buf = LBSM+        ( S.inlinePerformIO $ allocBuffer (max minSize defaultBufferSize)+        , bufToLBSCont buf )+    -- add bytestring directly as a chunk; exploits postcondition of runPut+    -- that bytestrings are non-empty+    outputBS bs = LBSM ((), L.Chunk bs)+++{-+-- | A Builder that traces a message+traceBuilder :: String -> Builder +traceBuilder msg = fromBuildStepCont $ \k br@(BufRange op ope) -> do+    putStrLn $ "traceBuilder " ++ show (op, ope) ++ ": " ++ msg+    k br++test2 :: Word8 -> [S.ByteString]+test2 x = L.toChunks $ toLazyByteString2 $ fromBuilder $ mconcat+  [ traceBuilder "before flush" +  , fromWord8 48+  , flushBuilder+  , flushBuilder+  , traceBuilder "after flush"+  , fromWord8 x+  ]++-}++-}
+ Blaze/ByteString/Builder/Internal/Buffer.hs view
@@ -0,0 +1,214 @@+{-# LANGUAGE CPP, BangPatterns, Rank2Types #-}+-- |+-- Module      : Blaze.ByteString.Builder.Internal.Buffer+-- Copyright   : (c) 2010 Simon Meier+-- License     : BSD3-style (see LICENSE)+-- +-- Maintainer  : Simon Meier <iridcode@gmail.com>+-- Stability   : experimental+-- Portability : tested on GHC only+--+-- Execution of the 'Put' monad and hence also 'Builder's with respect to+-- buffers.+--+module Blaze.ByteString.Builder.Internal.Buffer (+  -- * Buffers+    Buffer++  -- ** Status information+  , freeSize +  , sliceSize +  , bufferSize ++  -- ** Creation and modification+  , allocBuffer +  , reuseBuffer +  , nextSlice +  , updateEndOfSlice+  , execBuildStep++  -- ** Conversion to bytestings+  , unsafeFreezeBuffer +  , unsafeFreezeNonEmptyBuffer ++  -- * Buffer allocation strategies+  , BufferAllocStrategy+  , allNewBuffersStrategy +  , reuseBufferStrategy ++  -- * Executing puts respect to some monad+  , runPut+  ) where++import Prelude+import Foreign++import qualified Data.ByteString      as S++#ifdef BYTESTRING_IN_BASE+import qualified Data.ByteString.Base as S+#else+import qualified Data.ByteString.Internal as S+#endif++import Blaze.ByteString.Builder.Internal.Types+------------------------------------------------------------------------------+-- Buffers+------------------------------------------------------------------------------++-- | A buffer @Buffer fpbuf p0 op ope@ describes a buffer with the underlying+-- byte array @fpbuf..ope@, the currently written slice @p0..op@ and the free+-- space @op..ope@.+data Buffer = Buffer {-# UNPACK #-} !(ForeignPtr Word8) -- underlying pinned array+                     {-# UNPACK #-} !(Ptr Word8)        -- beginning of slice+                     {-# UNPACK #-} !(Ptr Word8)        -- next free byte+                     {-# UNPACK #-} !(Ptr Word8)        -- first byte after buffer++-- | The size of the free space of the buffer.+freeSize :: Buffer -> Int+freeSize (Buffer _ _ op ope) = ope `minusPtr` op++-- | The size of the written slice in the buffer.+sliceSize :: Buffer -> Int+sliceSize (Buffer _ p0 op _) = op `minusPtr` p0++-- | The size of the whole byte array underlying the buffer.+bufferSize :: Buffer -> Int+bufferSize (Buffer fpbuf _ _ ope) = +    ope `minusPtr` unsafeForeignPtrToPtr fpbuf++-- | @allocBuffer size@ allocates a new buffer of size @size@.+{-# INLINE allocBuffer #-}+allocBuffer :: Int -> IO Buffer+allocBuffer size = do+    fpbuf <- S.mallocByteString size+    let !pbuf = unsafeForeignPtrToPtr fpbuf+    return $! Buffer fpbuf pbuf pbuf (pbuf `plusPtr` size)++-- | Resets the beginning of the next slice and the next free byte such that+-- the whole buffer can be filled again.+{-# INLINE reuseBuffer #-}+reuseBuffer :: Buffer -> Buffer+reuseBuffer (Buffer fpbuf _ _ ope) = Buffer fpbuf p0 p0 ope+  where+    p0 = unsafeForeignPtrToPtr fpbuf++-- | Convert the buffer to a bytestring. This operation is unsafe in the sense+-- that created bytestring shares the underlying byte array with the buffer.+-- Hence, depending on the later use of this buffer (e.g., if it gets reset and+-- filled again) referential transparency may be lost.+{-# INLINE unsafeFreezeBuffer #-}+unsafeFreezeBuffer :: Buffer -> S.ByteString+unsafeFreezeBuffer (Buffer fpbuf p0 op _) = +    S.PS fpbuf (p0 `minusPtr` unsafeForeignPtrToPtr fpbuf) (op `minusPtr` p0)++-- | Convert a buffer to a non-empty bytestring. See 'unsafeFreezeBuffer' for+-- the explanation of why this operation may be unsafe.+{-# INLINE unsafeFreezeNonEmptyBuffer #-}+unsafeFreezeNonEmptyBuffer :: Buffer -> Maybe S.ByteString+unsafeFreezeNonEmptyBuffer buf+  | sliceSize buf <= 0 = Nothing+  | otherwise          = Just $ unsafeFreezeBuffer buf++-- | Update the end of slice pointer.+{-# INLINE updateEndOfSlice #-}+updateEndOfSlice :: Buffer    -- Old buffer+                 -> Ptr Word8 -- New end of slice  +                 -> Buffer    -- Updated buffer+updateEndOfSlice (Buffer fpbuf p0 _ ope) op' = Buffer fpbuf p0 op' ope++-- | Execute a build step on the given buffer.+{-# INLINE execBuildStep #-}+execBuildStep :: BuildStep a+              -> Buffer  +              -> IO (BuildSignal a)+execBuildStep step (Buffer _ _ op ope) = runBuildStep step (BufRange op ope)++-- | Move the beginning of the slice to the next free byte such that the+-- remaining free space of the buffer can be filled further. This operation+-- is safe and can be used to fill the remaining part of the buffer after a+-- direct insertion of a bytestring or a flush.+{-# INLINE nextSlice #-}+nextSlice :: Int -> Buffer -> Maybe Buffer+nextSlice minSize (Buffer fpbuf _ op ope)+  | ope `minusPtr` op <= minSize = Nothing+  | otherwise                    = Just (Buffer fpbuf op op ope)++------------------------------------------------------------------------------+-- Buffer allocation strategies+------------------------------------------------------------------------------++-- | A buffer allocation strategy @(buf0, nextBuf)@ specifies the initial+-- buffer to use and how to compute a new buffer @nextBuf minSize buf@ with at+-- least size @minSize@ from a filled buffer @buf@. The double nesting of the+-- @IO@ monad helps to ensure that the reference to the filled buffer @buf@ is+-- lost as soon as possible, but the new buffer doesn't have to be allocated +-- too early.+type BufferAllocStrategy = (IO Buffer, Int -> Buffer -> IO (IO Buffer))+  +-- | The simplest buffer allocation strategy: whenever a buffer is requested,+-- allocate a new one that is big enough for the next build step to execute.+--+-- NOTE that this allocation strategy may spill quite some memory upon direct+-- insertion of a bytestring by the builder. Thats no problem for garbage+-- collection, but it may lead to unreasonably high memory consumption in+-- special circumstances.+allNewBuffersStrategy :: Int                 -- Minimal buffer size.+                      -> BufferAllocStrategy+allNewBuffersStrategy bufSize = +    ( allocBuffer bufSize+    , \reqSize _ -> return (allocBuffer (max reqSize bufSize)) )++-- | An unsafe, but possibly more efficient buffer allocation strategy:+-- reuse the buffer, if it is big enough for the next build step to execute.+reuseBufferStrategy :: IO Buffer          +                    -> BufferAllocStrategy+reuseBufferStrategy buf0 =+    (buf0, tryReuseBuffer)+  where+    tryReuseBuffer reqSize buf+      | bufferSize buf >= reqSize = return $ return (reuseBuffer buf)+      | otherwise                 = return $ allocBuffer reqSize++------------------------------------------------------------------------------+-- Executing puts on a buffer+------------------------------------------------------------------------------+++-- | Execute a put on a buffer.+--+-- TODO: Generalize over buffer allocation strategy.+{-# INLINE runPut #-}+runPut :: Monad m +       => (IO (BuildSignal a) -> m (BuildSignal a)) -- lifting of buildsteps+       -> (Int -> Buffer -> m Buffer) -- output function for a guaranteedly non-empty buffer, the returned buffer will be filled next+       -> (S.ByteString -> m ())    -- output function for guaranteedly non-empty bytestrings, that are inserted directly into the stream+       -> Put a                     -- put to execute+       -> Buffer                    -- initial buffer to be used+       -> m (a, Buffer)             -- result of put and remaining buffer+runPut liftIO outputBuf outputBS (Put put) =+    runStep (put (finalStep))+  where+    finalStep x = buildStep $ \(BufRange op _) -> return $ Done op x++    runStep step buf@(Buffer fpbuf p0 op ope) = do+        let !br = BufRange op ope+        signal <- liftIO $ runBuildStep step br+        case signal of +            Done op' x ->         -- put completed, buffer partially runSteped+                return (x, Buffer fpbuf p0 op' ope)++            BufferFull minSize op' nextStep -> do+                buf' <- outputBuf minSize (Buffer fpbuf p0 op' ope)+                runStep nextStep buf'++            InsertByteString op' bs nextStep+              | S.null bs ->   -- flushing of buffer required+                  outputBuf 1 (Buffer fpbuf p0 op' ope) >>= runStep nextStep+              | p0 == op' -> do -- no bytes written: just insert bytestring+                  outputBS bs+                  runStep nextStep buf+              | otherwise -> do   -- bytes written, insert buffer and bytestring+                  buf' <- outputBuf 1 (Buffer fpbuf p0 op' ope)+                  outputBS bs+                  runStep nextStep buf'
+ Blaze/ByteString/Builder/Internal/Types.hs view
@@ -0,0 +1,137 @@+{-# LANGUAGE CPP, BangPatterns, Rank2Types #-}+-- |+-- Module      : Blaze.ByteString.Builder.Internal.Types+-- Copyright   : (c) 2010 Simon Meier+-- License     : BSD3-style (see LICENSE)+-- +-- Maintainer  : Simon Meier <iridcode@gmail.com>+-- Stability   : experimental+-- Portability : tested on GHC only+--+-- Core types and functions for the 'Builder' monoid and the 'Put' monad based+-- based on the 'blaze-builder' library by Jasper van der Jeugt and Simon+-- Meier.+--+module Blaze.ByteString.Builder.Internal.Types where++#ifdef APPLICATIVE_IN_BASE+import Control.Applicative+#endif++import Data.Monoid+import qualified Data.ByteString      as S++import Foreign++------------------------------------------------------------------------------+-- The core: BuildSteps+------------------------------------------------------------------------------++data BufRange = BufRange {-# UNPACK #-} !(Ptr Word8) {-# UNPACK #-} !(Ptr Word8)++data BuildSignal a =+    Done {-# UNPACK #-} !(Ptr Word8) a+  | BufferFull+      {-# UNPACK #-} !Int+      {-# UNPACK #-} !(Ptr Word8)+                     !(BuildStep a)+  | InsertByteString+      {-# UNPACK #-} !(Ptr Word8) +                     !S.ByteString+                     !(BuildStep a)++newtype BuildStep a =  +    BuildStep { runBuildStep :: BufRange -> IO (BuildSignal a) }++-- Hiding the implementation of 'BuildStep's++done :: Ptr Word8 -> a -> BuildSignal a+done = Done++bufferFull :: Int -> Ptr Word8 -> (BufRange -> IO (BuildSignal a)) -> BuildSignal a+bufferFull size op step = BufferFull size op (buildStep step)++insertByteString :: Ptr Word8 -> S.ByteString -> (BufRange -> IO (BuildSignal a)) -> BuildSignal a+insertByteString op bs step = InsertByteString op bs (buildStep step)++buildStep :: (BufRange -> IO (BuildSignal a)) -> BuildStep a+buildStep = BuildStep++------------------------------------------------------------------------------+-- The 'Builder' Monoid and the 'Put' Monad+------------------------------------------------------------------------------++newtype Builder = Builder { +    unBuilder :: forall r. BuildStep r -> BuildStep r +  }++instance Monoid Builder where+  mempty = Builder id+  {-# INLINE mempty #-}+  (Builder b1) `mappend` (Builder b2) = Builder $ b1 . b2+  {-# INLINE mappend #-}+  mconcat = foldr mappend mempty+  {-# INLINE mconcat #-}++newtype Put a = Put {+    unPut :: forall r. (a -> BuildStep r) -> BuildStep r+  }++instance Functor Put where+  fmap f (Put put) = Put $ \k -> put (\x -> k (f x))+  {-# INLINE fmap #-}++#ifdef APPLICATIVE_IN_BASE+instance Applicative Put where+  pure x = Put $ \k -> k x+  {-# INLINE pure #-}+  f <*> a = Put $ \k -> unPut f (\f' -> unPut a (\a' -> k (f' a')))+  {-# INLINE (<*>) #-}+  a <* b = Put $ \k -> unPut a (\a' -> unPut b (\_ -> k a'))+  {-# INLINE (<*) #-}+  a *> b = Put $ \k -> unPut a (\_ -> unPut b k)+  {-# INLINE (*>) #-}+#endif++instance Monad Put where+  return x = Put $ \k -> k x+  {-# INLINE return #-}+  m >>= f  = Put $ \k -> unPut m (\m' -> unPut (f m') k)+  {-# INLINE (>>=) #-}+  m >>  n  = Put $ \k -> unPut m (\_ -> unPut n k)+  {-# INLINE (>>) #-}+++-- Creation from concrete 'BuildStep's+------------------------------------------------------------------------------++putBuildStepCont :: (forall r. (a -> BufRange -> IO (BuildSignal r)) -> +                               (     BufRange -> IO (BuildSignal r))+                    ) -> Put a+putBuildStepCont step = Put step'+  where+    step' k = BuildStep $ step (\x -> runBuildStep (k x))+++fromBuildStepCont :: (forall r. (BufRange -> IO (BuildSignal r)) -> +                                (BufRange -> IO (BuildSignal r))+                     ) -> Builder+fromBuildStepCont step = Builder step'+  where+    step' k = BuildStep $ step (runBuildStep k)++++-- Conversion between Put and Builder+------------------------------------------------------------------------------++-- | Put the given builder.+putBuilder :: Builder -> Put ()+putBuilder (Builder build) = Put $ \k -> build (k ())+++-- | Ignore the value of a put and only exploit its output side effect.+fromPut :: Put a -> Builder+fromPut (Put put) = Builder $ \k -> put (\_ -> k)++
+ Blaze/ByteString/Builder/Internal/UncheckedShifts.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE CPP, MagicHash #-}+-- |+-- Module      : Blaze.ByteString.Builder.Internal.UncheckedShifts+-- Copyright   : (c) 2010 Simon Meier+--+--               Original serialization code from 'Data.Binary.Builder':+--               (c) Lennart Kolmodin, Ross Patterson+--+-- License     : BSD3-style (see LICENSE)+-- +-- Maintainer  : Simon Meier <iridcode@gmail.com>+-- Stability   : experimental+-- Portability : tested on GHC only+--+-- Utilty module defining unchecked shifts.+--+#if defined(__GLASGOW_HASKELL__) && !defined(__HADDOCK__)+#include "MachDeps.h"+#endif++module Blaze.ByteString.Builder.Internal.UncheckedShifts (+    shiftr_w16+  , shiftr_w32+  , shiftr_w64+  ) where++-- TODO: Check validity of this implementation++#if defined(__GLASGOW_HASKELL__) && !defined(__HADDOCK__)+import GHC.Base+import GHC.Word (Word32(..),Word16(..),Word64(..))++#if WORD_SIZE_IN_BITS < 64 && __GLASGOW_HASKELL__ >= 608+import GHC.Word (uncheckedShiftRL64#)+#endif+#else+import Data.Word+#endif+++------------------------------------------------------------------------+-- Unchecked shifts++{-# INLINE shiftr_w16 #-}+shiftr_w16 :: Word16 -> Int -> Word16+{-# INLINE shiftr_w32 #-}+shiftr_w32 :: Word32 -> Int -> Word32+{-# INLINE shiftr_w64 #-}+shiftr_w64 :: Word64 -> Int -> Word64++#if defined(__GLASGOW_HASKELL__) && !defined(__HADDOCK__)+shiftr_w16 (W16# w) (I# i) = W16# (w `uncheckedShiftRL#`   i)+shiftr_w32 (W32# w) (I# i) = W32# (w `uncheckedShiftRL#`   i)++#if WORD_SIZE_IN_BITS < 64+shiftr_w64 (W64# w) (I# i) = W64# (w `uncheckedShiftRL64#` i)++#if __GLASGOW_HASKELL__ <= 606+-- Exported by GHC.Word in GHC 6.8 and higher+foreign import ccall unsafe "stg_uncheckedShiftRL64"+    uncheckedShiftRL64#     :: Word64# -> Int# -> Word64#+#endif++#else+shiftr_w64 (W64# w) (I# i) = W64# (w `uncheckedShiftRL#` i)+#endif++#else+shiftr_w16 = shiftR+shiftr_w32 = shiftR+shiftr_w64 = shiftR+#endif+
+ Blaze/ByteString/Builder/Internal/Write.hs view
@@ -0,0 +1,187 @@+{-# LANGUAGE CPP, BangPatterns #-}+-- |+-- Module      : Blaze.ByteString.Builder.Internal.WriteIO+-- Copyright   : (c) 2010 Simon Meier+--               (c) 2010 Jasper van der Jeugt+-- License     : BSD3-style (see LICENSE)+-- +-- Maintainer  : Simon Meier <iridcode@gmail.com>+-- Stability   : experimental+-- Portability : tested on GHC only+--+-- A general and efficient write type that allows for the easy construction of+-- builders for (smallish) bounded size writes to a buffer.+--+module Blaze.ByteString.Builder.Internal.Write (+  -- * Abstracting writes to a buffer+    Write+  , WriteIO+  , writeN+  , exactWrite+  , boundedWrite+  , runWrite+  , runWriteIO++  -- * Constructing builders from writes+  , fromWrite+  , fromWriteSingleton+  , fromWriteList++  -- * Writing 'Storable's+  , writeStorable+  , fromStorable+  , fromStorables++  ) where++import Foreign++import Data.Monoid++import Control.Monad++import Blaze.ByteString.Builder.Internal.Types+++------------------------------------------------------------------------------+-- The Write WriteIO Type+------------------------------------------------------------------------------++-- Sadly GHC is not smart enough: code where we branch and each branch should+-- execute a few IO actions and then return a value cannot be taught to GHC. At+-- least not such that it returns the value of the branches unpacked.+--+-- Hmm.. at least he behaves much better for the Monoid instance of Write+-- than the one for WriteIO. Serializing UTF-8 chars gets a slowdown of a+-- factor 2 when 2 chars are composed. Perhaps I should try out the writeList+-- instances also, as they may be more sensitive to to much work per Char.+--++-- | A write to a buffer.+--+-- FIXME: Find better name: what about 'Poke' ?+newtype WriteIO = +    WriteIO { runWriteIO :: Ptr Word8 -> IO (Ptr Word8) }++-- | A write of a bounded number of bytes.+data Write = Write {-# UNPACK #-} !Int WriteIO++-- | Extract the 'WriteIO' action of a write.+{-# INLINE runWrite #-}+runWrite :: Write -> WriteIO+runWrite (Write _ wio) = wio++instance Monoid WriteIO where+  mempty = WriteIO $ return+  {-# INLINE mempty #-}+  (WriteIO w1) `mappend` (WriteIO w2) = WriteIO $ w1 >=> w2+  {-# INLINE mappend #-}+  mconcat = foldr mappend mempty+  {-# INLINE mconcat #-}++instance Monoid Write where+  mempty = Write 0 mempty+  {-# INLINE mempty #-}+  (Write bound1 w1) `mappend` (Write bound2 w2) =+    Write (bound1 + bound2) (w1 `mappend` w2)+  {-# INLINE mappend #-}+  mconcat = foldr mappend mempty+  {-# INLINE mconcat #-}+++-- | @writeN size io@ creates a write that denotes the writing of @size@ bytes+-- to a buffer using the IO action @io@. Note that @io@ MUST write EXACTLY @size@+-- bytes to the buffer!+writeN :: Int +       -> (Ptr Word8 -> IO ()) -> WriteIO+writeN size io = WriteIO $ \op -> io op >> return (op `plusPtr` size)+{-# INLINE writeN #-}+++-- | @exactWrite size io@ creates a bounded write that can later be converted to+-- a builder that writes exactly @size@ bytes. Note that @io@ MUST write+-- EXACTLY @size@ bytes to the buffer!+exactWrite :: Int +           -> (Ptr Word8 -> IO ()) +           -> Write+exactWrite size io = Write size (writeN size io)+{-# INLINE exactWrite #-}++-- | @boundedWrite size write@ creates a bounded write from a @write@ that does+-- not write more than @size@ bytes.+boundedWrite :: Int -> WriteIO -> Write+boundedWrite = Write+{-# INLINE boundedWrite #-}++fromWrite :: Write -> Builder+fromWrite (Write maxSize wio) =+    fromBuildStepCont step+  where+    step k (BufRange op ope)+      | op `plusPtr` maxSize <= ope = do+          op' <- runWriteIO wio op+          let !br' = BufRange op' ope+          k br'+      | otherwise = return $ bufferFull maxSize op (step k)+{-# INLINE fromWrite #-}++fromWriteSingleton :: (a -> Write) -> a -> Builder+fromWriteSingleton write = +    mkBuilder+  where+    mkBuilder x = fromBuildStepCont step+      where+        step k (BufRange op ope)+          | op `plusPtr` maxSize <= ope = do+              op' <- runWriteIO wio op+              let !br' = BufRange op' ope+              k br'+          | otherwise = return $ bufferFull maxSize op (step k)+          where+            Write maxSize wio = write x+{-# INLINE fromWriteSingleton #-}++-- | Construct a 'Builder' writing a list of data one element at a time.+fromWriteList :: (a -> Write) -> [a] -> Builder+fromWriteList write = +    makeBuilder+  where+    makeBuilder xs0 = fromBuildStepCont $ step xs0+      where+        step xs1 k !(BufRange op0 ope0) = go xs1 op0+          where+            go [] !op = do+               let !br' = BufRange op ope0+               k br'++            go xs@(x':xs') !op+              | op `plusPtr` maxSize <= ope0 = do+                  !op' <- runWriteIO wio op+                  go xs' op'+              | otherwise = return $ bufferFull maxSize op (step xs k)+              where+                Write maxSize wio = write x'+{-# INLINE fromWriteList #-}++++------------------------------------------------------------------------------+-- Writing storables+------------------------------------------------------------------------------+++-- | Write a storable value.+{-# INLINE writeStorable #-}+writeStorable :: Storable a => a -> Write +writeStorable x = exactWrite (sizeOf x) (\op -> poke (castPtr op) x)++-- | A builder that serializes a storable value. No alignment is done.+{-# INLINE fromStorable #-}+fromStorable :: Storable a => a -> Builder+fromStorable = fromWriteSingleton writeStorable++-- | A builder that serializes a list of storable values by writing them+-- consecutively. No alignment is done. Parsing information needs to be+-- provided externally.+fromStorables :: Storable a => [a] -> Builder+fromStorables = fromWriteList writeStorable
Blaze/ByteString/Builder/Word.hs view
@@ -1,6 +1,3 @@-{-# OPTIONS_GHC -fglasgow-exts #-}--- for unboxed shifts- {-# LANGUAGE CPP #-} -- | -- Module      : Blaze.ByteString.Builder.Word@@ -34,7 +31,6 @@ #if defined(__GLASGOW_HASKELL__) && !defined(__HADDOCK__) #include "MachDeps.h" #endif- module Blaze.ByteString.Builder.Word     (      -- * Writing words to a buffer@@ -62,8 +58,7 @@     -- | We provide serialization functions both for singleton words as well as     -- for lists of words. Using these list serialization functions is /much/ faster     -- than using @mconcat . map fromWord/<n/>@, as the list serialization-    -- functions use a tighter inner loop and a chunked write like it is-    -- provided by functions such as 'fromWrite2List'.+    -- functions use a tighter inner loop.      , fromWord8     , fromWord8s@@ -97,17 +92,12 @@     ) where  import Blaze.ByteString.Builder.Internal-import Blaze.ByteString.Builder.Write+import Blaze.ByteString.Builder.Internal.UncheckedShifts  import Foreign  #if defined(__GLASGOW_HASKELL__) && !defined(__HADDOCK__)-import GHC.Base import GHC.Word (Word32(..),Word16(..),Word64(..))--#if WORD_SIZE_IN_BITS < 64 && __GLASGOW_HASKELL__ >= 608-import GHC.Word (uncheckedShiftRL64#)-#endif #else import Data.Word #endif@@ -125,7 +115,7 @@ -- | Write a single byte. -- writeWord8 :: Word8 -> Write-writeWord8 x = Write 1 (\pf -> poke pf x)+writeWord8 x = exactWrite 1 (\pf -> poke pf x) {-# INLINE writeWord8 #-}  --@@ -135,23 +125,23 @@  -- | Write a 'Word16' in big endian format. writeWord16be :: Word16 -> Write-writeWord16be w = Write 2 $ \p -> do+writeWord16be w = exactWrite 2 $ \p -> do     poke p               (fromIntegral (shiftr_w16 w 8) :: Word8)     poke (p `plusPtr` 1) (fromIntegral (w)              :: Word8) {-# INLINE writeWord16be #-}  -- | Write a 'Word16' in little endian format. writeWord16le :: Word16 -> Write-writeWord16le w = Write 2 $ \p -> do+writeWord16le w = exactWrite 2 $ \p -> do     poke p               (fromIntegral (w)              :: Word8)     poke (p `plusPtr` 1) (fromIntegral (shiftr_w16 w 8) :: Word8) {-# INLINE writeWord16le #-} --- writeWord16le w16 = Write 2 (\p -> poke (castPtr p) w16)+-- writeWord16le w16 = exactWrite 2 (\p -> poke (castPtr p) w16)  -- | Write a 'Word32' in big endian format. writeWord32be :: Word32 -> Write-writeWord32be w = Write 4 $ \p -> do+writeWord32be w = exactWrite 4 $ \p -> do     poke p               (fromIntegral (shiftr_w32 w 24) :: Word8)     poke (p `plusPtr` 1) (fromIntegral (shiftr_w32 w 16) :: Word8)     poke (p `plusPtr` 2) (fromIntegral (shiftr_w32 w  8) :: Word8)@@ -160,7 +150,7 @@  -- | Write a 'Word32' in little endian format. writeWord32le :: Word32 -> Write-writeWord32le w = Write 4 $ \p -> do+writeWord32le w = exactWrite 4 $ \p -> do     poke p               (fromIntegral (w)               :: Word8)     poke (p `plusPtr` 1) (fromIntegral (shiftr_w32 w  8) :: Word8)     poke (p `plusPtr` 2) (fromIntegral (shiftr_w32 w 16) :: Word8)@@ -168,7 +158,7 @@ {-# INLINE writeWord32le #-}  -- on a little endian machine:--- writeWord32le w32 = Write 4 (\p -> poke (castPtr p) w32)+-- writeWord32le w32 = exactWrite 4 (\p -> poke (castPtr p) w32)  -- | Write a 'Word64' in big endian format. writeWord64be :: Word64 -> Write@@ -180,7 +170,7 @@ writeWord64be w =     let a = fromIntegral (shiftr_w64 w 32) :: Word32         b = fromIntegral w                 :: Word32-    in Write 8 $ \p -> do+    in exactWrite 8 $ \p -> do     poke p               (fromIntegral (shiftr_w32 a 24) :: Word8)     poke (p `plusPtr` 1) (fromIntegral (shiftr_w32 a 16) :: Word8)     poke (p `plusPtr` 2) (fromIntegral (shiftr_w32 a  8) :: Word8)@@ -190,7 +180,7 @@     poke (p `plusPtr` 6) (fromIntegral (shiftr_w32 b  8) :: Word8)     poke (p `plusPtr` 7) (fromIntegral (b)               :: Word8) #else-writeWord64be w = Write 8 $ \p -> do+writeWord64be w = exactWrite 8 $ \p -> do     poke p               (fromIntegral (shiftr_w64 w 56) :: Word8)     poke (p `plusPtr` 1) (fromIntegral (shiftr_w64 w 48) :: Word8)     poke (p `plusPtr` 2) (fromIntegral (shiftr_w64 w 40) :: Word8)@@ -209,7 +199,7 @@ writeWord64le w =     let b = fromIntegral (shiftr_w64 w 32) :: Word32         a = fromIntegral w                 :: Word32-    in Write 8 $ \p -> do+    in exactWrite 8 $ \p -> do     poke (p)             (fromIntegral (a)               :: Word8)     poke (p `plusPtr` 1) (fromIntegral (shiftr_w32 a  8) :: Word8)     poke (p `plusPtr` 2) (fromIntegral (shiftr_w32 a 16) :: Word8)@@ -219,7 +209,7 @@     poke (p `plusPtr` 6) (fromIntegral (shiftr_w32 b 16) :: Word8)     poke (p `plusPtr` 7) (fromIntegral (shiftr_w32 b 24) :: Word8) #else-writeWord64le w = Write 8 $ \p -> do+writeWord64le w = exactWrite 8 $ \p -> do     poke p               (fromIntegral (w)               :: Word8)     poke (p `plusPtr` 1) (fromIntegral (shiftr_w64 w  8) :: Word8)     poke (p `plusPtr` 2) (fromIntegral (shiftr_w64 w 16) :: Word8)@@ -232,7 +222,7 @@ {-# INLINE writeWord64le #-}  -- on a little endian machine:--- writeWord64le w64 = Write 8 (\p -> poke (castPtr p) w64)+-- writeWord64le w64 = exactWrite 8 (\p -> poke (castPtr p) w64)  ------------------------------------------------------------------------ -- Unaligned, word size ops@@ -245,61 +235,28 @@ -- writeWordhost :: Word -> Write writeWordhost w = -    Write (sizeOf (undefined :: Word)) (\p -> poke (castPtr p) w)+    exactWrite (sizeOf (undefined :: Word)) (\p -> poke (castPtr p) w) {-# INLINE writeWordhost #-}  -- | Write a 'Word16' in native host order and host endianness. writeWord16host :: Word16 -> Write writeWord16host w16 = -    Write (sizeOf (undefined :: Word16)) (\p -> poke (castPtr p) w16)+    exactWrite (sizeOf (undefined :: Word16)) (\p -> poke (castPtr p) w16) {-# INLINE writeWord16host #-}  -- | Write a 'Word32' in native host order and host endianness. writeWord32host :: Word32 -> Write writeWord32host w32 = -    Write (sizeOf (undefined :: Word32)) (\p -> poke (castPtr p) w32)+    exactWrite (sizeOf (undefined :: Word32)) (\p -> poke (castPtr p) w32) {-# INLINE writeWord32host #-}  -- | Write a 'Word64' in native host order and host endianness. writeWord64host :: Word64 -> Write writeWord64host w = -    Write (sizeOf (undefined :: Word64)) (\p -> poke (castPtr p) w)+    exactWrite (sizeOf (undefined :: Word64)) (\p -> poke (castPtr p) w) {-# INLINE writeWord64host #-} ---------------------------------------------------------------------------- Unchecked shifts -{-# INLINE shiftr_w16 #-}-shiftr_w16 :: Word16 -> Int -> Word16-{-# INLINE shiftr_w32 #-}-shiftr_w32 :: Word32 -> Int -> Word32-{-# INLINE shiftr_w64 #-}-shiftr_w64 :: Word64 -> Int -> Word64--#if defined(__GLASGOW_HASKELL__) && !defined(__HADDOCK__)-shiftr_w16 (W16# w) (I# i) = W16# (w `uncheckedShiftRL#`   i)-shiftr_w32 (W32# w) (I# i) = W32# (w `uncheckedShiftRL#`   i)--#if WORD_SIZE_IN_BITS < 64-shiftr_w64 (W64# w) (I# i) = W64# (w `uncheckedShiftRL64#` i)--#if __GLASGOW_HASKELL__ <= 606--- Exported by GHC.Word in GHC 6.8 and higher-foreign import ccall unsafe "stg_uncheckedShiftRL64"-    uncheckedShiftRL64#     :: Word64# -> Int# -> Word64#-#endif--#else-shiftr_w64 (W64# w) (I# i) = W64# (w `uncheckedShiftRL#` i)-#endif--#else-shiftr_w16 = shiftR-shiftr_w32 = shiftR-shiftr_w64 = shiftR-#endif-- ------------------------------------------------------------------------------ -- Builders corresponding to the word writes ------------------------------------------------------------------------------@@ -315,7 +272,7 @@ -- | Serialize a list of bytes. -- fromWord8s :: [Word8] -> Builder-fromWord8s = fromWrite8List writeWord8+fromWord8s = fromWriteList writeWord8   -- Word16@@ -328,7 +285,7 @@  -- | Serialize a list of 'Word16's in big endian format. fromWord16sbe :: [Word16] -> Builder-fromWord16sbe = fromWrite4List writeWord16be +fromWord16sbe = fromWriteList writeWord16be  {-# INLINE fromWord16sbe #-}  -- | Serialize a 'Word16' in little endian format.@@ -338,7 +295,7 @@  -- | Serialize a list of 'Word16's in little endian format. fromWord16sle :: [Word16] -> Builder-fromWord16sle = fromWrite4List writeWord16le +fromWord16sle = fromWriteList writeWord16le  {-# INLINE fromWord16sle #-}  @@ -352,7 +309,7 @@  -- | Serialize a list of 'Word32's in big endian format. fromWord32sbe :: [Word32] -> Builder-fromWord32sbe = fromWrite2List writeWord32be +fromWord32sbe = fromWriteList writeWord32be  {-# INLINE fromWord32sbe #-}  -- | Serialize a 'Word32' in little endian format.@@ -362,7 +319,7 @@  -- | Serialize a list of 'Word32's in little endian format. fromWord32sle :: [Word32] -> Builder-fromWord32sle = fromWrite2List writeWord32le +fromWord32sle = fromWriteList writeWord32le  {-# INLINE fromWord32sle #-}  -- | Serialize a 'Word64' in big endian format.@@ -372,7 +329,7 @@  -- | Serialize a list of 'Word64's in big endian format. fromWord64sbe :: [Word64] -> Builder-fromWord64sbe = fromWrite1List writeWord64be +fromWord64sbe = fromWriteList writeWord64be  {-# INLINE fromWord64sbe #-}  -- | Serialize a 'Word64' in little endian format.@@ -382,7 +339,7 @@  -- | Serialize a list of 'Word64's in little endian format. fromWord64sle :: [Word64] -> Builder-fromWord64sle = fromWrite1List writeWord64le +fromWord64sle = fromWriteList writeWord64le  {-# INLINE fromWord64sle #-}  @@ -402,7 +359,7 @@ -- | Serialize a list of 'Word's. -- See 'fromWordhost' for usage considerations. fromWordshost :: [Word] -> Builder-fromWordshost = fromWrite2List writeWordhost +fromWordshost = fromWriteList writeWordhost  {-# INLINE fromWordshost #-}  -- | Write a 'Word16' in native host order and host endianness.@@ -412,7 +369,7 @@  -- | Write a list of 'Word16's in native host order and host endianness. fromWord16shost :: [Word16] -> Builder-fromWord16shost = fromWrite4List writeWord16host +fromWord16shost = fromWriteList writeWord16host  {-# INLINE fromWord16shost #-}  -- | Write a 'Word32' in native host order and host endianness.@@ -422,7 +379,7 @@  -- | Write a list of 'Word32's in native host order and host endianness. fromWord32shost :: [Word32] -> Builder-fromWord32shost = fromWrite2List writeWord32host +fromWord32shost = fromWriteList writeWord32host  {-# INLINE fromWord32shost #-}  -- | Write a 'Word64' in native host order and host endianness.@@ -432,5 +389,5 @@  -- | Write a list of 'Word64's in native host order and host endianness. fromWord64shost :: [Word64] -> Builder-fromWord64shost = fromWrite1List writeWord64host+fromWord64shost = fromWriteList writeWord64host {-# INLINE fromWord64shost #-}
− Blaze/ByteString/Builder/Write.hs
@@ -1,384 +0,0 @@-{-# LANGUAGE CPP, BangPatterns #-}---- |--- Module      : Blaze.ByteString.Builder.Write--- Copyright   : (c) 2010 Jasper Van der Jeugt & Simon Meier--- License     : BSD3-style (see LICENSE)--- --- Maintainer  : Simon Meier <iridcode@gmail.com>--- Stability   : experimental--- Portability : tested on GHC only------ This module provides the 'Write' type, which abstracts direct writes to a--- buffer. 'Write's form the public interface for lifting direct buffer--- manipulations to 'Builder's.----module Blaze.ByteString.Builder.Write-    ( -    -- * Atomic writes to a buffer-      Write (..)--    -- * Creating builders from 'Write' abstractions-    , fromWrite-    , fromWriteSingleton-    , fromWrite1List-    , fromWrite2List-    , fromWrite4List-    , fromWrite8List-    , fromWrite16List--    ) where--import Blaze.ByteString.Builder.Internal--import Foreign-import Data.Monoid ------------------------------------------------------------------------------------ Atomic writes to a buffer----------------------------------------------------------------------------------- | A value @Write n io@ denotes the write of @n@ bytes to a buffer. The--- actual write is executed by calling @io@ with a pointer @pf@ to the first--- free byte that the write should start with. Note that the caller of @io pf@--- must ensure that @n@ bytes are free starting from @pf@.------ For example, the function @'writeWord8'@ provided by--- "Blaze.ByteString.Builder.Word" creates a 'Write' that writes a single fixed byte--- to a buffer.------ > writeWord8   :: Word8 -> Write--- > writeWord8 x  = Write 1 (\pf -> poke pf x)------ The benefit of writes is that they abstract low-level manipulations (e.g.--- 'poke' and 'copyBytes') of sequences of bytes in a form that that can be--- completely optimized away in many cases.------ For example, the 'Monoid' instance of 'Write' allows to formulate writing a--- three-tuple of bytes as follows.------ > writeThreeWord8   :: (Word8, Word8, Word8) -> Write--- > writeThreeWord8 (x,y,z) = --- >     writeWord8 x `mappend` writeWord8 y `mappend` writeWord8 z------ This expression will be optimized by the compiler to the following efficient--- 'Write'.------ > writeThreeWord8 (x, y, z) = Write 3 $ \pf -> do--- >     poke pf               x--- >     poke (pf `plusPtr` 1) y--- >     poke (pf `plusPtr` 2) z------ Writes are /atomic/. This means that the written data cannot be wrapped--- over buffer boundaries as it can be done for builders. For writes it holds--- that either the buffer has enough free space and the write can proceed or a--- new buffer with a size larger or equal to the number of bytes to write has--- to be allocated.------ Moreover, for a 'Write', the size of the data to be written must be known--- before the data can be written. Hence, if this size is data-dependent, the--- control flow becomes complicated: first, all data must be forced and stored,--- then the size check happens, and only afterwards the stored data can be--- written. Therefore, because of cache misses, composing writes with--- data-dependent size computations may actually be slower than combining the--- resulting builders. Use benchmarking to make informed decisions.----data Write = Write-    {-# UNPACK #-} !Int  -- Number of bytes that will be written.-    (Ptr Word8 -> IO ()) -- Function to write the bytes starting from the given-                         -- pointer---- A monoid interface for the 'Write' actions.-instance Monoid Write where-    mempty = Write 0 (const $ return ())-    {-# INLINE mempty #-}--    mappend (Write l1 f1) (Write l2 f2) = Write (l1 + l2) $ \ptr -> do-        f1 ptr-        f2 (ptr `plusPtr` l1)-    {-# INLINE mappend #-}----- Lifting Writes to Builders---------------------------------- | Create a 'Builder' from a single write @w@. For good performance, @w@ must--- feature an outermost 'Write' constructor such that the pattern match can be--- eliminated during compilation.------ Semantically, it holds that------ > fromWrite . write = fromWriteSingleton write------ However, performance-wise the right-hand side is more efficient due to--- currently unknown reasons. Use the second form, when--- defining functions for creating builders from writes of Haskell values.------ (Use the standard benchmark in the @blaze-html@ package when investigating--- this phenomenon.)-fromWrite :: Write -> Builder-fromWrite (Write size io) =-    Builder step-  where-    step k pf pe-      | pf `plusPtr` size <= pe = do-          io pf-          let pf' = pf `plusPtr` size-          pf' `seq` k pf' pe-      | otherwise               = return $ BufferFull size pf (step k)-{-# INLINE fromWrite #-}---- | Create a 'Builder' constructor from a single 'Write' constructor.----fromWriteSingleton :: (a -> Write) -> a -> Builder   -fromWriteSingleton write = makeBuilder-  where -    makeBuilder x = Builder step-      where-        step k pf pe-          | pf `plusPtr` size <= pe = do-              io pf-              let pf' = pf `plusPtr` size-              pf' `seq` k pf' pe-          | otherwise               = return $ BufferFull size pf (step k)-          where-            Write size io = write x-{-# INLINE fromWriteSingleton #-}---- | Construct a 'Builder' writing a list of data one element at a time from a 'Write' abstraction.----fromWrite1List :: (a -> Write) -> [a] -> Builder-fromWrite1List write = makeBuilder-  where-    makeBuilder []  = mempty-    makeBuilder xs0 = Builder $ step xs0-      where-        step xs1 k pf0 pe0 = go xs1 pf0-          where-            go []          !pf = k pf pe0-            go xs@(x':xs') !pf-              | pf `plusPtr` size <= pe0  = do-                  io pf-                  go xs' (pf `plusPtr` size)-              | otherwise = do return $ BufferFull size pf (step xs k)-              where-                Write size io = write x'-{-# INLINE fromWrite1List #-}---- | Construct a 'Builder' writing a list of data two elements at a time from a--- 'Write' abstraction.----fromWrite2List :: (a -> Write) -> [a] -> Builder-fromWrite2List write = makeBuilder-  where-    makeBuilder []  = mempty-    makeBuilder xs0 = Builder $ step xs0-      where-        step xs1 k pf0 pe0 = go xs1 pf0-          where-            go []       !pf = k pf pe0--            go xs@[x'1] !pf-              | pf' <= pe0  = do-                  io pf-                  k pf' pe0-              | otherwise   = do return $ BufferFull size pf (step xs k)-              where-                Write size io = write x'1-                pf' = pf `plusPtr` size--            go xs@(x'1:x'2:xs') !pf-              | pf' <= pe0  = do-                  io pf-                  go xs' pf'-              | otherwise = do return $ BufferFull size pf (step xs k)-              where-                Write size io = write x'1 `mappend` write x'2-                pf' = pf `plusPtr` size-{-# INLINE fromWrite2List #-}---- | Construct a 'Builder' writing a list of data four elements at a time from a--- 'Write' abstraction.----fromWrite4List :: (a -> Write) -> [a] -> Builder-fromWrite4List write = makeBuilder-  where-    makeBuilder []  = mempty-    makeBuilder xs0 = Builder $ step xs0-      where-        step xs1 k pf0 pe0 = go xs1 pf0-          where-            go xs@(x'1:x'2:x'3:x'4:xs') !pf-              | pf' <= pe0  = do-                  io pf-                  go xs' pf'-              | otherwise = do return $ BufferFull size pf (step xs k)-              where-                Write size io = write x'1 `mappend` write x'2-                                          `mappend` write x'3 -                                          `mappend` write x'4 -                pf' = pf `plusPtr` size--            go xs@(x'1:x'2:xs') !pf-              | pf' <= pe0  = do-                  io pf-                  go xs' pf'-              | otherwise = do return $ BufferFull size pf (step xs k)-              where-                Write size io = write x'1 `mappend` write x'2-                pf' = pf `plusPtr` size--            go xs@[x'1] !pf-              | pf' <= pe0  = do-                  io pf-                  k pf' pe0-              | otherwise   = do return $ BufferFull size pf (step xs k)-              where-                Write size io = write x'1-                pf' = pf `plusPtr` size--            go [] !pf = k pf pe0-{-# INLINE fromWrite4List #-}---- | Construct a 'Builder' writing a list of data eight elements at a time from a--- 'Write' abstraction.----fromWrite8List :: (a -> Write) -> [a] -> Builder-fromWrite8List write = makeBuilder-  where-    makeBuilder []  = mempty-    makeBuilder xs0 = Builder $ step xs0-      where-        step xs1 k pf0 pe0 = go xs1 pf0-          where-            go xs@(x'1:x'2:x'3:x'4:x'5:x'6:x'7:x'8:xs') !pf-              | pf' <= pe0  = do-                  io pf-                  go xs' pf'-              | otherwise = do return $ BufferFull size pf (step xs k)-              where-                Write size io = write x'1 `mappend` write x'2-                                          `mappend` write x'3 -                                          `mappend` write x'4 -                                          `mappend` write x'5 -                                          `mappend` write x'6 -                                          `mappend` write x'7 -                                          `mappend` write x'8 -                pf' = pf `plusPtr` size--            go xs@(x'1:x'2:x'3:x'4:xs') !pf-              | pf' <= pe0  = do-                  io pf-                  go xs' pf'-              | otherwise = do return $ BufferFull size pf (step xs k)-              where-                Write size io = write x'1 `mappend` write x'2-                                          `mappend` write x'3 -                                          `mappend` write x'4 -                pf' = pf `plusPtr` size--            go xs@(x'1:x'2:xs') !pf-              | pf' <= pe0  = do-                  io pf-                  go xs' pf'-              | otherwise = do return $ BufferFull size pf (step xs k)-              where-                Write size io = write x'1 `mappend` write x'2-                pf' = pf `plusPtr` size--            go xs@[x'1] !pf-              | pf' <= pe0  = do-                  io pf-                  k pf' pe0-              | otherwise   = do return $ BufferFull size pf (step xs k)-              where-                Write size io = write x'1-                pf' = pf `plusPtr` size--            go [] !pf = k pf pe0-{-# INLINE fromWrite8List #-}---- | Construct a 'Builder' writing a list of data 16 elements at a time from a--- 'Write' abstraction.----fromWrite16List :: (a -> Write) -> [a] -> Builder-fromWrite16List write = makeBuilder-  where-    makeBuilder []  = mempty-    makeBuilder xs0 = Builder $ step xs0-      where-        step xs1 k pf0 pe0 = go xs1 pf0-          where-            go xs@(x'1:x'2:x'3:x'4:x'5:x'6:x'7:x'8:x'9:x'10:x'11:x'12:x'13:x'14:x'15:x'16:xs') !pf-              | pf' <= pe0  = do-                  io pf-                  go xs' pf'-              | otherwise = do return $ BufferFull size pf (step xs k)-              where-                Write size io = write x'1 `mappend` write x'2-                                          `mappend` write x'3 -                                          `mappend` write x'4 -                                          `mappend` write x'5 -                                          `mappend` write x'6 -                                          `mappend` write x'7 -                                          `mappend` write x'8 -                                          `mappend` write x'9 -                                          `mappend` write x'10-                                          `mappend` write x'11-                                          `mappend` write x'12-                                          `mappend` write x'13-                                          `mappend` write x'14-                                          `mappend` write x'15-                                          `mappend` write x'16-                pf' = pf `plusPtr` size--            go xs@(x'1:x'2:x'3:x'4:x'5:x'6:x'7:x'8:xs') !pf-              | pf' <= pe0  = do-                  io pf-                  go xs' pf'-              | otherwise = do return $ BufferFull size pf (step xs k)-              where-                Write size io = write x'1 `mappend` write x'2-                                          `mappend` write x'3 -                                          `mappend` write x'4 -                                          `mappend` write x'5 -                                          `mappend` write x'6 -                                          `mappend` write x'7 -                                          `mappend` write x'8 -                pf' = pf `plusPtr` size---            go xs@(x'1:x'2:x'3:x'4:xs') !pf-              | pf' <= pe0  = do-                  io pf-                  go xs' pf'-              | otherwise = do return $ BufferFull size pf (step xs k)-              where-                Write size io = write x'1 `mappend` write x'2-                                          `mappend` write x'3 -                                          `mappend` write x'4 -                pf' = pf `plusPtr` size--            go xs@(x'1:x'2:xs') !pf-              | pf' <= pe0  = do-                  io pf-                  go xs' pf'-              | otherwise = do return $ BufferFull size pf (step xs k)-              where-                Write size io = write x'1 `mappend` write x'2-                pf' = pf `plusPtr` size--            go xs@[x'1] !pf-              | pf' <= pe0  = do-                  io pf-                  k pf' pe0-              | otherwise   = do return $ BufferFull size pf (step xs k)-              where-                Write size io = write x'1-                pf' = pf `plusPtr` size--            go [] !pf = k pf pe0-{-# INLINE fromWrite16List #-}--
CHANGES view
@@ -1,3 +1,32 @@+* 0.2.1.0++  Incorporated several design changes:+    - Writable buffer range is now represented in a packed form. This improves+      speed slightly, as less currying is used.+    - Writes are abstracted such that their internal representation can be+      exchanged without breaking other library code.+    - Writes are represented in a form that allows for efficient monoid+      instances for branching code like UTF-8 encoding. For single character+      encoding this results currently in a slight slowdown due to GHC not+      recognizing the strictness of the returned value. This will be fixed in+      the future.+    - BuildSteps support returning a result in `Done`, which enables to +      implement a `Put` monad using CPS.+    - chunked list writes were removed, as they result in worse performance+      when writing non-trivial lists. (cf. benchmarks)+    - An internal buffering abstraction is introduced, which is used both+      by the adaption of the `binary` package, as well as by the+      `blaze-builder-enumeratee` package, to execute puts and builders.+      It will be used later also by the execution functions of the+      `blaze-builder` package.+  +  Implemented new functionality+    - `Blaze.ByteString.Builder.HTTP` provides a builder transformer for +       doing in-buffer chunked HTTP encoding of an arbitary other builder.+    - `Blaze.ByteString.Builder.Char8` provides functions to serialize the+       lower 8-bits of characters similiar to what `Data.ByteString.Char8`+       provides for bytestrings.+ * 0.2.0.3      Loosen 'text' dependency to '>= 0.10 && < 0.12'
Makefile view
@@ -6,9 +6,11 @@ ## Config ######### -GHC = ghc-6.12.3-# GHC = ghc-7.0.0.20100924+GHC6 = ghc-6.12.3+GHC7 = ghc-7.0.1 +GHC = $(GHC7)+ GHCI = ghci-6.12.3  @@ -67,6 +69,49 @@ 	$(GHC) --make -O2 -fforce-recomp -ibenchmarks -main-is Compression Compression 	./benchmarks/Compression --resamples 10000 +# Benchmark the use of unboxed continuation calls+bench-unboxed-append:+	$(GHC) --make -O2 -fforce-recomp -ibenchmarks -main-is UnboxedAppend UnboxedAppend+	./benchmarks/UnboxedAppend --resamples 10000++# Core of the use of unboxed continuation calls+core-unboxed-append:+	ghc-core -- --make -O2 -fforce-recomp -main-is UnboxedAppend benchmarks/UnboxedAppend.hs++# Benchmark the cost of the Put monad vs. the Builder monoid+bench-put-vs-builder:+	$(GHC) --make -O2 -fforce-recomp -ibenchmarks -main-is FastPut FastPut+	./benchmarks/FastPut --resamples 10000++# Benchmark the cost/benefit of a more general write type+bench-bounded-write:+	$(GHC7) --make -O2 -fforce-recomp -ibenchmarks -main-is BoundedWrite BoundedWrite+	./benchmarks/BoundedWrite --resamples 10000++core-bounded-write:+	ghc-core -- --make -O2 -fforce-recomp -main-is BoundedWrite benchmarks/BoundedWrite.hs+++# Benchmark the benefit of using a packed representation for the buffer range+bench-buffer-range:+	$(GHC) --make -O2 -fforce-recomp -ibenchmarks -main-is BuilderBufferRange BuilderBufferRange+	./benchmarks/BuilderBufferRange --resamples 10000++# Benchmark improvements to lazy bytestring functions+bench-lazy-bytestring:+	$(GHC) --make -O2 -fforce-recomp -ibenchmarks -main-is LazyByteString LazyByteString+	./benchmarks/LazyByteString --resamples 10000++# Benchmark benefit of compaction before compression+bench-server:+	$(GHC) --make -O2 -ibenchmarks -main-is BenchmarkServer BenchmarkServer+	# ./benchmarks/BenchmarkServer --resamples 10000+	./benchmarks/BenchmarkServer 9999 100000 +RTS -s&+	ab -n 1000 localhost:9999/lbs+	curl localhost:9999/kill > /dev/null 2>&1+++ ############################################################################## ## Plots ##############################################################################@@ -81,7 +126,7 @@ ##############################################################################  test:-	$(GHC) --make -O2 -itests -main-is Tests Tests+	$(GHC) --make -fforce-recomp -O2 -itests -main-is Tests Tests 	./tests/Tests  clean-tests:
TODO view
@@ -1,3 +1,7 @@++!! UPDATE TODO !!++!! UPDATE BENCHMARKS !!      * custom serialization functions for lists of 'WordX's       - benchmark chunk size speedup for more complicated computations of list@@ -19,6 +23,12 @@         cheaper than one list thunk per element. It is even likely that we can         amortize three copies per element, which allows to avoid spilling any         buffer space by doing a last compaction copy.+      - we could provide builders that honor alignment restrictions, either as+        builder transformers or as specialized write to builder converters. The+        trick is for the driver to ensure that the buffer beginning is aligned+        to the largest aligning (8 or 16 bytes?) required. This is probably the+        case by default. Then we can always align a pointer in the buffer by +        appropriately aligning the write pointer.    * extend tests to new functions 
+ benchmarks/BenchmarkServer.hs view
@@ -0,0 +1,97 @@+{- Benchmark server based upon Jasper van der Jeugt's 'BenchmarkServer.lhs'+   from blaze-html. Modified for network-2.3 by Simon Meier <iridcode@gmail.com>+-}++{-# LANGUAGE OverloadedStrings #-}+module BenchmarkServer where++import Prelude hiding (putStrLn)++import Data.Char   (ord)+import Data.Monoid +import Data.ByteString.Char8 () -- IsString instance only+import qualified Data.ByteString               as S+import qualified Data.ByteString.Lazy          as L+import qualified Data.ByteString.Lazy.Internal as L++import Control.Concurrent (forkIO, putMVar, takeMVar, newEmptyMVar)+import Control.Exception  (bracket)+import Control.Monad ++import Network.Socket   (Socket, accept, sClose)+import Network          (listenOn, PortID (PortNumber))+import Network.Socket.ByteString      as S+import Network.Socket.ByteString.Lazy as L++import System (getArgs)++import Blaze.ByteString.Builder+import Blaze.ByteString.Builder.Internal (defaultBufferSize, defaultMinimalBufferSize)+import Blaze.ByteString.Builder.Char.Utf8++import Criterion.Main++httpOkHeader :: S.ByteString +httpOkHeader = S.concat +    [ "HTTP/1.1 200 OK\r\n"+    , "Content-Type: text/html; charset=UTF-8\r\n"+    , "\r\n" ]++response :: Int -> Builder+response n = +  fromByteString httpOkHeader `mappend` +  fromString (take n $ cycle "hello λ-world! ")++sendVectoredBuilderLBS :: Socket -> Builder -> IO ()+sendVectoredBuilderLBS s = L.sendAll s . toLazyByteString+{-# NOINLINE sendVectoredBuilderLBS #-}++sendBuilderLBS :: Socket -> Builder -> IO ()+sendBuilderLBS s = +  -- mapM_ (S.sendAll s) . L.toChunks . toLazyByteString+  L.foldrChunks (\c -> (S.sendAll s c >>)) (return ()). toLazyByteString+{-# NOINLINE sendBuilderLBS #-}++sendBuilderBSIO :: Socket -> Builder -> IO ()+sendBuilderBSIO s = toByteStringIO $ S.sendAll s+{-# NOINLINE sendBuilderBSIO #-}++-- criterion benchmark determining the speed of response+main2 = defaultMain+    [ bench ("response " ++ show n) $ whnf +        (L.length . toLazyByteString . response) n+    ]+  where+    n :: Int+    n = 1000000++main :: IO ()+main = do+    [port, nChars] <- map read `liftM` getArgs+    killSignal <- newEmptyMVar+    bracket (listenOn . PortNumber . fromIntegral $ port) sClose +        (\socket -> do+            _ <- forkIO $ loop (putMVar killSignal ()) nChars socket+            takeMVar killSignal)+  where+    loop killServer nChars socket = forever $ do +        (s, _) <- accept socket+        forkIO (respond s nChars)+      where+        respond s n = do+            input <- S.recv s 1024+            let requestUrl = (S.split (fromIntegral $ ord ' ') input) !! 1+            case tail (S.split (fromIntegral $ ord '/') requestUrl) of+                ["lbs"]     -> sendBuilderLBS s         $ response n+                ["lbs-vec"] -> sendVectoredBuilderLBS s $ response n+                ["bs-io"]   -> sendBuilderBSIO   s      $ response n+                ["kill"]    -> notFound s >> killServer+                _           -> notFound s+            sClose s++    notFound s = do+        _ <- S.sendAll s $ "HTTP/1.1 404 Not Found\r\n"+            `mappend` "Content-Type: text/html; charset=UTF-8\r\n"+            `mappend` "\r\n"+            `mappend` "<h1>Page not found</h1>"+        return ()
benchmarks/BlazeVsBinary.hs view
@@ -42,7 +42,7 @@ main :: IO () main = defaultMain $ concat     [ benchmark "[String]"-        (mconcat . concatMap (map $ Binary.singleton .  fromIntegral . ord))+        (mconcat . map (mconcat . (map $ Binary.singleton .  fromIntegral . ord)))         (mconcat . map Blaze.fromString)         strings     , benchmark "L.ByteString"
+ benchmarks/BoundedWrite.hs view
@@ -0,0 +1,241 @@+{-# LANGUAGE CPP, BangPatterns #-}+-- |+-- Module      : BoundedWrite+-- Copyright   : (c) 2010 Simon Meier+-- License     : BSD3-style (see LICENSE)+-- +-- Maintainer  : Simon Meier <iridcode@gmail.com>+-- Stability   : experimental+-- Portability : tested on GHC only+--+-- A more general/efficient write type.+--+module BoundedWrite (main) where++import Foreign+import Data.Monoid+import Data.Char++import Foreign.UPtr++import qualified Data.ByteString.Internal as S+import qualified Data.ByteString.Lazy as L++import Blaze.ByteString.Builder.Internal+import Blaze.ByteString.Builder.Write +import Blaze.ByteString.Builder.Word ++import Criterion.Main++------------------------------------------------------------------------------+-- Benchmarks+------------------------------------------------------------------------------++main :: IO ()+main = defaultMain $ concat+    {-+    [ benchmark "mconcat . map (fromWriteSingleton writeChar)"+        bfrom3Chars+        from3Chars+        chars3+    ]+    -}+    [ benchmark "mconcat . map fromWord8"+        (mconcat . map bfromWord8)+        (mconcat . map fromWord8)+        word8s+    ]+  where+    benchmark name boundedF staticF x =+        [ bench (name ++ " <- bounded write") $+            whnf (L.length . toLazyByteString . boundedF) x+        , bench (name ++ " <- static write") $+            whnf (L.length . toLazyByteString . staticF) x+        ]++word8s :: [Word8]+word8s = take 100000 $ cycle [0..]+{-# NOINLINE word8s #-}++chars :: [Char]+chars = take 100000 $ ['\0'..]+{-# NOINLINE chars #-}++chars2 :: [(Char,Char)]+chars2 = zip chars chars+{-# NOINLINE chars2 #-}++chars3 :: [(Char, Char, Char)]+chars3 = zip3 chars (reverse chars) (reverse chars)+{-# NOINLINE chars3 #-}++bfromChars = (mconcat . map (fromBWriteSingleton bwriteChar))+{-# NOINLINE bfromChars #-}++fromChars = (mconcat . map (fromWriteSingleton writeChar))+{-# NOINLINE fromChars #-}++bfrom2Chars = (mconcat . map (fromBWriteSingleton (\(c1, c2) -> bwriteChar c1 `mappend` bwriteChar c2)))+{-# NOINLINE bfrom2Chars #-}++from2Chars = (mconcat . map (fromWriteSingleton (\(c1, c2) -> writeChar c1 `mappend` writeChar c2)))+{-# NOINLINE from2Chars #-}++bfrom3Chars = (mconcat . map (fromBWriteSingleton (\(c1, c2, c3) -> bwriteChar c1 `mappend` bwriteChar c2 `mappend` bwriteChar c3)))+{-# NOINLINE bfrom3Chars #-}++from3Chars = (mconcat . map (fromWriteSingleton (\(c1, c2, c3) -> writeChar c1 `mappend` writeChar c2 `mappend` writeChar c3)))+{-# NOINLINE from3Chars #-}++------------------------------------------------------------------------------+-- The Bounded Write Type+------------------------------------------------------------------------------++-- * GRRR* GHC is too 'clever'... code where we branch and each branch should+-- execute a few IO actions and then return a value cannot be taught to GHC. +-- At least not such that it returns the value of the branches unpacked.+--+-- Hmm.. at least he behaves much better for the Monoid instance of BWrite+-- than the one for Write. Serializing UTF-8 chars gets a slowdown of a+-- factor 2 when 2 chars are composed. Perhaps I should try out the writeList+-- instances also, as they may be more sensitive to to much work per Char.+--+data BWrite = BWrite {-# UNPACK #-} !Int (UPtr -> UPtr)++newtype UWrite = UWrite { runUWrite :: UPtr -> UPtr }++instance Monoid UWrite where+  mempty = UWrite $ \x -> x+  {-# INLINE mempty #-}+  (UWrite uw1) `mappend` (UWrite uw2) = UWrite (\up -> uw2 (uw1 up))+  {-# INLINE mappend #-}++instance Monoid BWrite where+  mempty = BWrite 0 (\x -> x)+  {-# INLINE mempty #-}+  (BWrite b1 io1) `mappend` (BWrite b2 io2) =+    BWrite (b1 + b2) (\op -> io2 (io1 op))+  {-# INLINE mappend #-}++execWrite :: IO () -> UPtr -> UPtr+execWrite io op' = S.inlinePerformIO io `seq` op'+{-# INLINE execWrite #-}++execWriteSize :: (Ptr Word8 -> IO ()) -> Int -> UPtr -> UPtr+execWriteSize io size op = execWrite (io (uptrToPtr op)) (op `plusUPtr` size)+{-# INLINE execWriteSize #-}++staticBWrite :: Int -> (Ptr Word8 -> IO ()) -> BWrite+staticBWrite size io = BWrite size (execWriteSize io size)+{-# INLINE staticBWrite #-}++bwriteWord8 :: Word8 -> BWrite +bwriteWord8 x = staticBWrite 1 (`poke` x)+{-# INLINE bwriteWord8 #-}++fromBWrite :: BWrite -> Builder+fromBWrite (BWrite size io) =+    Builder step+  where+    step k !pf !pe+      | pf `plusPtr` size <= pe = do+          let !pf' = io (ptrToUPtr pf)+          k (uptrToPtr pf') pe+      | otherwise = return $ BufferFull size pf (step k)+{-# INLINE fromBWrite #-}++fromBWriteSingleton :: (a -> BWrite) -> a -> Builder+fromBWriteSingleton write = +    mkPut+  where+    mkPut x = Builder step+      where+        step k !pf !pe+          | pf `plusPtr` size <= pe = do+              let !pf' = io (ptrToUPtr pf)+              k (uptrToPtr pf') pe+          | otherwise               = return $ BufferFull size pf (step k)+          where+            BWrite size io = write x+{-# INLINE fromBWriteSingleton #-}++bfromWord8 :: Word8 -> Builder+bfromWord8 = fromBWriteSingleton bwriteWord8++-- Utf-8 encoding+-----------------++bwriteChar :: Char -> BWrite+bwriteChar c = BWrite 4 (encodeCharUtf8 f1 f2 f3 f4 c)+  where+    f1 x =  \uptr -> execWrite (do let !ptr = uptrToPtr uptr+                                   poke ptr x )+                               (uptr `plusUPtr` 1)++    f2 x1 x2 = \uptr -> execWrite (do let !ptr = uptrToPtr uptr+                                      poke ptr x1+                                      poke (ptr `plusPtr` 1) x2 )+                                  (uptr `plusUPtr` 2)++    f3 x1 x2 x3 = \uptr -> execWrite (do let !ptr = uptrToPtr uptr+                                         poke ptr x1+                                         poke (ptr `plusPtr` 1) x2+                                         poke (ptr `plusPtr` 2) x3 )+                                     (uptr `plusUPtr` 3)++    f4 x1 x2 x3 x4 = \uptr -> execWrite (do let !ptr = uptrToPtr uptr+                                            poke ptr x1+                                            poke (ptr `plusPtr` 1) x2+                                            poke (ptr `plusPtr` 2) x3+                                            poke (ptr `plusPtr` 3) x4 )+                                        (uptr `plusUPtr` 4)+{-# INLINE bwriteChar #-}++writeChar :: Char -> Write+writeChar = encodeCharUtf8 f1 f2 f3 f4+  where+    f1 x = Write 1 $ \ptr -> poke ptr x++    f2 x1 x2 = Write 2 $ \ptr -> do poke ptr x1+                                    poke (ptr `plusPtr` 1) x2++    f3 x1 x2 x3 = Write 3 $ \ptr -> do poke ptr x1+                                       poke (ptr `plusPtr` 1) x2+                                       poke (ptr `plusPtr` 2) x3++    f4 x1 x2 x3 x4 = Write 4 $ \ptr -> do poke ptr x1+                                          poke (ptr `plusPtr` 1) x2+                                          poke (ptr `plusPtr` 2) x3+                                          poke (ptr `plusPtr` 3) x4+{-# INLINE writeChar #-}++-- | Encode a Unicode character to another datatype, using UTF-8. This function+-- acts as an abstract way of encoding characters, as it is unaware of what+-- needs to happen with the resulting bytes: you have to specify functions to+-- deal with those.+--+encodeCharUtf8 :: (Word8 -> a)                             -- ^ 1-byte UTF-8+               -> (Word8 -> Word8 -> a)                    -- ^ 2-byte UTF-8+               -> (Word8 -> Word8 -> Word8 -> a)           -- ^ 3-byte UTF-8+               -> (Word8 -> Word8 -> Word8 -> Word8 -> a)  -- ^ 4-byte UTF-8+               -> Char                                     -- ^ Input 'Char'+               -> a                                        -- ^ Result+encodeCharUtf8 f1 f2 f3 f4 c = case ord c of+    x | x <= 0x7F -> f1 $ fromIntegral x+      | x <= 0x07FF ->+           let x1 = fromIntegral $ (x `shiftR` 6) + 0xC0+               x2 = fromIntegral $ (x .&. 0x3F)   + 0x80+           in f2 x1 x2+      | x <= 0xFFFF ->+           let x1 = fromIntegral $ (x `shiftR` 12) + 0xE0+               x2 = fromIntegral $ ((x `shiftR` 6) .&. 0x3F) + 0x80+               x3 = fromIntegral $ (x .&. 0x3F) + 0x80+           in f3 x1 x2 x3+      | otherwise ->+           let x1 = fromIntegral $ (x `shiftR` 18) + 0xF0+               x2 = fromIntegral $ ((x `shiftR` 12) .&. 0x3F) + 0x80+               x3 = fromIntegral $ ((x `shiftR` 6) .&. 0x3F) + 0x80+               x4 = fromIntegral $ (x .&. 0x3F) + 0x80+           in f4 x1 x2 x3 x4+{-# INLINE encodeCharUtf8 #-}+
+ benchmarks/BuilderBufferRange.hs view
@@ -0,0 +1,463 @@+{-# LANGUAGE CPP, BangPatterns #-}+-- |+-- Module      : BuilderBufferRange+-- Copyright   : (c) 2010 Simon Meier+-- License     : BSD3-style (see LICENSE)+-- +-- Maintainer  : Simon Meier <iridcode@gmail.com>+-- Stability   : experimental+-- Portability : tested on GHC only+--+-- Benchmark the benefit of using a packed representation for the buffer range.+--+module BuilderBufferRange where+++import Foreign+import Data.Monoid+import Control.Monad (unless)+import qualified Data.ByteString      as S+import qualified Data.ByteString.Lazy as L++#ifdef BYTESTRING_IN_BASE+import Data.ByteString.Base (inlinePerformIO)+import qualified Data.ByteString.Base as S+import qualified Data.ByteString.Lazy.Base as L -- FIXME: is this the right module for access to 'Chunks'?+#else+import Data.ByteString.Internal (inlinePerformIO)+import qualified Data.ByteString.Internal as S+import qualified Data.ByteString.Lazy.Internal as L+#endif++import qualified Blaze.ByteString.Builder.Internal as B+import Blaze.ByteString.Builder.Write+import Blaze.ByteString.Builder.Word++import Criterion.Main++------------------------------------------------------------------------------+-- Benchmarks+------------------------------------------------------------------------------++main :: IO ()+main = defaultMain $ concat+    [ benchmark "putBuilder"+        (putBuilder . mconcat . map fromWord8)+        (mconcat . map fromWord8)+        word8s+    , benchmark "fromWriteSingleton"+        (mconcat . map  putWord8)+        (mconcat . map fromWord8)+        word8s+    , benchmark "fromWrite"+        (mconcat . map (putWrite . writeWord8))+        (mconcat . map (fromWrite . writeWord8))+        word8s+    ]+  where+    benchmark name putF builderF x =+        [ bench (name ++ " Put") $+            whnf (L.length . toLazyByteString . putF) x+        , bench (name ++ " Builder") $+            whnf (L.length . B.toLazyByteString . builderF) x+        ]++word8s :: [Word8]+word8s = take 100000 $ cycle [0..]+{-# NOINLINE word8s #-}+++------------------------------------------------------------------------------+-- The Builder type+------------------------------------------------------------------------------++data BufferRange = BR {-# UNPACK #-} !(Ptr Word8) +                      {-# UNPACK #-} !(Ptr Word8)++newtype Put = Put (PutStep -> PutStep)++data PutSignal =+    Done {-# UNPACK #-} !(Ptr Word8)+  | BufferFull+      {-# UNPACK #-} !Int+      {-# UNPACK #-} !(Ptr Word8)+                     !PutStep+  | ModifyChunks+      {-# UNPACK #-} !(Ptr Word8) +                     !(L.ByteString -> L.ByteString) +                     !PutStep++type PutStep =  BufferRange -> IO PutSignal++instance Monoid Put where+  mempty = Put id+  {-# INLINE mempty #-}+  (Put p1) `mappend` (Put p2) = Put $ p1 . p2+  {-# INLINE mappend #-}+  mconcat = foldr mappend mempty+  {-# INLINE mconcat #-}++putWrite :: Write -> Put+putWrite (Write size io) =+    Put step+  where+    step k (BR pf pe)+      | pf `plusPtr` size <= pe = do+          io pf+          let !br' = BR (pf `plusPtr` size) pe+          k br'+      | otherwise = return $ BufferFull size pf (step k)+{-# INLINE putWrite #-}++putWriteSingleton :: (a -> Write) -> a -> Put+putWriteSingleton write = +    mkPut+  where+    mkPut x = Put step+      where+        step k (BR pf pe)+          | pf `plusPtr` size <= pe = do+              io pf+              let !br' = BR (pf `plusPtr` size) pe+              k br'+          | otherwise               = return $ BufferFull size pf (step k)+          where+            Write size io = write x+{-# INLINE putWriteSingleton #-}++putBuilder :: B.Builder -> Put+putBuilder (B.Builder b) = +    Put step+  where+    finalStep _ pf = return $ B.Done pf++    step k = go (b finalStep)+      where+        go buildStep (BR pf pe) = do+          signal <- buildStep pf pe+          case signal of+            B.Done pf' -> do+              let !br' = BR pf' pe+              k br'+            B.BufferFull minSize pf' nextBuildStep -> +              return $ BufferFull minSize pf' (go nextBuildStep)+            B.ModifyChunks _ _ _ -> +              error "putBuilder: ModifyChunks not implemented"++putWord8 :: Word8 -> Put+putWord8 = putWriteSingleton writeWord8++{-+  m >>= f  = GetC $ \done empty pe ->+      runGetC m (\pr' x -> runGetC (f x) done empty pe pr') +                (\m' -> empty (m' >>= f))+                pe+++newtype GetC r a = GetC {+    runGetC ::+      (Ptr Word8 -> a -> IO r) ->   -- done+      (GetC r a -> IO r     )  ->   -- empty buffer+      Ptr Word8                ->   -- end of buffer+      Ptr Word8                ->   -- next byte to read+      IO r+  }++instance Functor (GetC r) where+  fmap f g = GetC $ \done empty ->+      runGetC g (\pr' x -> done pr' (f x)) +                (\g'    -> empty (fmap f g'))++instance Monad (GetC r) where+  return x = GetC $ \done _ _ pr -> done pr x+  m >>= f  = GetC $ \done empty pe ->+      runGetC m (\pr' x -> runGetC (f x) done empty pe pr') +                (\m' -> empty (m' >>= f))+                pe++-}++------------------------------------------------------------------------------+-- Internal global constants.+------------------------------------------------------------------------------++-- | Default size (~32kb) for the buffer that becomes a chunk of the output+-- stream once it is filled.+--+defaultBufferSize :: Int+defaultBufferSize = 32 * 1024 - overhead -- Copied from Data.ByteString.Lazy.+    where overhead = 2 * sizeOf (undefined :: Int)++-- | The minimal length (~4kb) a buffer must have before filling it and+-- outputting it as a chunk of the output stream. +--+-- This size determines when a buffer is spilled after a 'flush' or a direct+-- bytestring insertion. It is also the size of the first chunk generated by+-- 'toLazyByteString'.+defaultMinimalBufferSize :: Int+defaultMinimalBufferSize = 4 * 1024 - overhead+    where overhead = 2 * sizeOf (undefined :: Int)++-- | The default length (64) for the first buffer to be allocated when+-- converting a 'Builder' to a lazy bytestring. +--+-- See 'toLazyByteStringWith' for further explanation.+defaultFirstBufferSize :: Int+defaultFirstBufferSize = 64++-- | The maximal number of bytes for that copying is cheaper than direct+-- insertion into the output stream. This takes into account the fragmentation+-- that may occur in the output buffer due to the early 'flush' implied by the+-- direct bytestring insertion.+--+-- @'defaultMaximalCopySize' = 2 * 'defaultMinimalBufferSize'@+--+defaultMaximalCopySize :: Int+defaultMaximalCopySize = 2 * defaultMinimalBufferSize++------------------------------------------------------------------------------+-- Flushing and running a Builder+------------------------------------------------------------------------------+++-- | Output all data written in the current buffer and start a new chunk.+--+-- The use uf this function depends on how the resulting bytestrings are+-- consumed. 'flush' is possibly not very useful in non-interactive scenarios.+-- However, it is kept for compatibility with the builder provided by+-- Data.Binary.Builder.+--+-- When using 'toLazyByteString' to extract a lazy 'L.ByteString' from a+-- 'Builder', this means that a new chunk will be started in the resulting lazy+-- 'L.ByteString'. The remaining part of the buffer is spilled, if the+-- reamining free space is smaller than the minimal desired buffer size.+--+{-+flush :: Builder+flush = Builder $ \k pf _ -> return $ ModifyChunks pf id k+-}++-- | Run a 'Builder' with the given buffer sizes.+--+-- Use this function for integrating the 'Builder' type with other libraries+-- that generate lazy bytestrings.+--+-- Note that the builders should guarantee that on average the desired chunk+-- size is attained. Builders may decide to start a new buffer and not+-- completely fill the existing buffer, if this is faster. However, they should+-- not spill too much of the buffer, if they cannot compensate for it.+--+-- A call @toLazyByteStringWith bufSize minBufSize firstBufSize@ will generate+-- a lazy bytestring according to the following strategy. First, we allocate+-- a buffer of size @firstBufSize@ and start filling it. If it overflows, we+-- allocate a buffer of size @minBufSize@ and copy the first buffer to it in+-- order to avoid generating a too small chunk. Finally, every next buffer will+-- be of size @bufSize@. This, slow startup strategy is required to achieve+-- good speed for short (<200 bytes) resulting bytestrings, as for them the+-- allocation cost is of a large buffer cannot be compensated. Moreover, this+-- strategy also allows us to avoid spilling too much memory for short+-- resulting bytestrings.+--+-- Note that setting @firstBufSize >= minBufSize@ implies that the first buffer+-- is no longer copied but allocated and filled directly. Hence, setting+-- @firstBufSize = bufSize@ means that all chunks will use an underlying buffer+-- of size @bufSize@. This is recommended, if you know that you always output+-- more than @minBufSize@ bytes.+toLazyByteStringWith +    :: Int           -- ^ Buffer size (upper-bounds the resulting chunk size).+    -> Int           -- ^ Minimal free buffer space for continuing filling+                     -- the same buffer after a 'flush' or a direct bytestring+                     -- insertion. This corresponds to the minimal desired+                     -- chunk size.+    -> Int           -- ^ Size of the first buffer to be used and copied for+                     -- larger resulting sequences+    -> Put           -- ^ Builder to run.+    -> L.ByteString  -- ^ Lazy bytestring to output after the builder is+                     -- finished.+    -> L.ByteString  -- ^ Resulting lazy bytestring+toLazyByteStringWith bufSize minBufSize firstBufSize (Put b) k = +    inlinePerformIO $ fillFirstBuffer (b finalStep)+  where+    finalStep (BR pf _) = return $ Done pf+    -- fill a first very small buffer, if we need more space then copy it+    -- to the new buffer of size 'minBufSize'. This way we don't pay the+    -- allocation cost of the big 'bufSize' buffer, when outputting only+    -- small sequences.+    fillFirstBuffer !step0+      | minBufSize <= firstBufSize = fillNewBuffer firstBufSize step0+      | otherwise                  = do+          fpbuf <- S.mallocByteString firstBufSize+          withForeignPtr fpbuf $ \pf -> do+              let !br      = BR pf (pf `plusPtr` firstBufSize)+                  mkbs pf' = S.PS fpbuf 0 (pf' `minusPtr` pf)+                  {-# INLINE mkbs #-}+              next <- step0 br+              case next of+                  Done pf'+                    | pf' == pf -> return k+                    | otherwise -> return $ L.Chunk (mkbs pf') k++                  BufferFull newSize pf' nextStep  -> do+                      let !l  = pf' `minusPtr` pf+                      fillNewBuffer (max (l + newSize) minBufSize) $+                          \(BR pfNew peNew) -> do+                              copyBytes pfNew pf l+                              let !brNew = BR (pfNew `plusPtr` l) peNew+                              nextStep brNew+                      +                  ModifyChunks pf' bsk nextStep +                      | pf' == pf ->+                          return $ bsk (inlinePerformIO $ fillNewBuffer bufSize nextStep)+                      | otherwise ->+                          return $ L.Chunk (mkbs pf')+                              (bsk (inlinePerformIO $ fillNewBuffer bufSize nextStep))+                    +    -- allocate and fill a new buffer+    fillNewBuffer !size !step0 = do+        fpbuf <- S.mallocByteString size+        withForeignPtr fpbuf $ fillBuffer fpbuf+      where+        fillBuffer fpbuf !pbuf = fill pbuf step0+          where+            !pe = pbuf `plusPtr` size+            fill !pf !step = do+                let !br = BR pf pe+                next <- step br+                let mkbs pf' = S.PS fpbuf (pf `minusPtr` pbuf) (pf' `minusPtr` pf)+                    {-# INLINE mkbs #-}+                case next of+                    Done pf'+                      | pf' == pf -> return k+                      | otherwise -> return $ L.Chunk (mkbs pf') k++                    BufferFull newSize pf' nextStep ->+                        return $ L.Chunk (mkbs pf')+                            (inlinePerformIO $ +                                fillNewBuffer (max newSize bufSize) nextStep)+                        +                    ModifyChunks  pf' bsk nextStep+                      | pf' == pf                      ->+                          return $ bsk (inlinePerformIO $ fill pf' nextStep)+                      | minBufSize < pe `minusPtr` pf' ->+                          return $ L.Chunk (mkbs pf')+                              (bsk (inlinePerformIO $ fill pf' nextStep))+                      | otherwise                      ->+                          return $ L.Chunk (mkbs pf')+                              (bsk (inlinePerformIO $ fillNewBuffer bufSize nextStep))+++-- | Extract the lazy 'L.ByteString' from the builder by running it with default+-- buffer sizes. Use this function, if you do not have any special+-- considerations with respect to buffer sizes.+--+-- @ 'toLazyByteString' b = 'toLazyByteStringWith' 'defaultBufferSize' 'defaultMinimalBufferSize' 'defaultFirstBufferSize' b L.empty@+--+-- Note that @'toLazyByteString'@ is a 'Monoid' homomorphism.+--+-- > toLazyByteString mempty          == mempty+-- > toLazyByteString (x `mappend` y) == toLazyByteString x `mappend` toLazyByteString y+--+-- However, in the second equation, the left-hand-side is generally faster to+-- execute.+--+toLazyByteString :: Put -> L.ByteString+toLazyByteString b = toLazyByteStringWith +    defaultBufferSize defaultMinimalBufferSize defaultFirstBufferSize b L.empty+{-# INLINE toLazyByteString #-}++{-+-- | Pack the chunks of a lazy bytestring into a single strict bytestring.+packChunks :: L.ByteString -> S.ByteString+packChunks lbs = do+    S.unsafeCreate (fromIntegral $ L.length lbs) (copyChunks lbs)+  where+    copyChunks !L.Empty                         !_pf = return ()+    copyChunks !(L.Chunk (S.PS fpbuf o l) lbs') !pf  = do+        withForeignPtr fpbuf $ \pbuf ->+            copyBytes pf (pbuf `plusPtr` o) l+        copyChunks lbs' (pf `plusPtr` l)++-- | Run the builder to construct a strict bytestring containing the sequence+-- of bytes denoted by the builder. This is done by first serializing to a lazy bytestring and then packing its+-- chunks to a appropriately sized strict bytestring.+--+-- > toByteString = packChunks . toLazyByteString+--+-- Note that @'toByteString'@ is a 'Monoid' homomorphism.+--+-- > toByteString mempty          == mempty+-- > toByteString (x `mappend` y) == toByteString x `mappend` toByteString y+--+-- However, in the second equation, the left-hand-side is generally faster to+-- execute.+--+toByteString :: Builder -> S.ByteString+toByteString = packChunks . toLazyByteString+++-- | @toByteStringIOWith bufSize io b@ runs the builder @b@ with a buffer of+-- at least the size @bufSize@ and executes the 'IO' action @io@ whenever the+-- buffer is full.+--+-- Compared to 'toLazyByteStringWith' this function requires less allocation,+-- as the output buffer is only allocated once at the start of the+-- serialization and whenever something bigger than the current buffer size has+-- to be copied into the buffer, which should happen very seldomly for the+-- default buffer size of 32kb. Hence, the pressure on the garbage collector is+-- reduced, which can be an advantage when building long sequences of bytes.+--+toByteStringIOWith :: Int                      -- ^ Buffer size (upper bounds+                                               -- the number of bytes forced+                                               -- per call to the 'IO' action).+                   -> (S.ByteString -> IO ())  -- ^ 'IO' action to execute per+                                               -- full buffer, which is+                                               -- referenced by a strict+                                               -- 'S.ByteString'.+                   -> Builder                  -- ^ 'Builder' to run.+                   -> IO ()                    -- ^ Resulting 'IO' action.+toByteStringIOWith bufSize io (Builder b) = +    fillNewBuffer bufSize (b finalStep)+  where+    finalStep pf _ = return $ Done pf++    fillNewBuffer !size !step0 = do+        S.mallocByteString size >>= fillBuffer+      where+        fillBuffer fpbuf = fill step0+          where+            -- safe because the constructed ByteString references the foreign+            -- pointer AFTER its buffer was filled.+            pf = unsafeForeignPtrToPtr fpbuf+            fill !step = do+                next <- step pf (pf `plusPtr` size)+                case next of+                    Done pf' ->+                        unless (pf' == pf) (io $  S.PS fpbuf 0 (pf' `minusPtr` pf))++                    BufferFull newSize pf' nextStep  -> do+                        io $ S.PS fpbuf 0 (pf' `minusPtr` pf)+                        if bufSize < newSize+                          then fillNewBuffer newSize nextStep+                          else fill nextStep+                        +                    ModifyChunks  pf' bsk nextStep  -> do+                        unless (pf' == pf) (io $  S.PS fpbuf 0 (pf' `minusPtr` pf))+                        -- was: mapM_ io $ L.toChunks (bsk L.empty)+                        L.foldrChunks (\bs -> (io bs >>)) (return ()) (bsk L.empty)+                        fill nextStep++-- | Run the builder with a 'defaultBufferSize'd buffer and execute the given+-- 'IO' action whenever the buffer is full or gets flushed.+--+-- @ 'toByteStringIO' = 'toByteStringIOWith' 'defaultBufferSize'@+--+-- This is a 'Monoid' homomorphism in the following sense.+--+-- > toByteStringIO io mempty          == return ()+-- > toByteStringIO io (x `mappend` y) == toByteStringIO io x >> toByteStringIO io y+--+toByteStringIO :: (S.ByteString -> IO ()) -> Builder -> IO ()+toByteStringIO = toByteStringIOWith defaultBufferSize+{-# INLINE toByteStringIO #-}++-}
+ benchmarks/FastPut.hs view
@@ -0,0 +1,643 @@+{-# LANGUAGE CPP, BangPatterns, Rank2Types #-}+-- |+-- Module      : FastPut+-- Copyright   : (c) 2010 Simon Meier+-- License     : BSD3-style (see LICENSE)+-- +-- Maintainer  : Simon Meier <iridcode@gmail.com>+-- Stability   : experimental+-- Portability : tested on GHC only+--+-- Implementation of a 'Put' monad with similar performance characteristics+-- like the 'Builder' monoid.+--+module FastPut where++import Foreign+import Data.Monoid+import Control.Monad (unless)+import qualified Data.ByteString      as S+import qualified Data.ByteString.Lazy as L++#ifdef BYTESTRING_IN_BASE+import Data.ByteString.Base (inlinePerformIO)+import qualified Data.ByteString.Base as S+import qualified Data.ByteString.Lazy.Base as L -- FIXME: is this the right module for access to 'Chunks'?+#else+import Data.ByteString.Internal (inlinePerformIO)+import qualified Data.ByteString.Internal as S+import qualified Data.ByteString.Lazy.Internal as L+#endif++import qualified Blaze.ByteString.Builder.Internal as B+import qualified Blaze.ByteString.Builder.Write    as B+import Blaze.ByteString.Builder.Write (Write(..))+import qualified Blaze.ByteString.Builder.Word     as B+import Blaze.ByteString.Builder.Word (writeWord8)++import Criterion.Main++------------------------------------------------------------------------------+-- Benchmarks+------------------------------------------------------------------------------++main :: IO ()+main = defaultMain $ concat+    [ return $ bench "cost of putBuilder" $ whnf+        (L.length . toLazyByteString2 . mapM_  (fromBuilder . fromWord8))+        word8s+    , benchmark "putBuilder"+        (fromBuilder . mconcat . map fromWord8)+        (mconcat . map B.fromWord8)+        word8s+    , benchmark "fromWriteSingleton"+        (mapM_ putWord8)+        (mconcat . map B.fromWord8)+        word8s+    , benchmark "fromWrite"+        (mapM_ (putWrite . writeWord8))+        (mconcat . map (B.fromWrite . writeWord8))+        word8s+    ]+  where+    benchmark name putF builderF x =+        [ bench (name ++ " Put") $+            whnf (L.length . toLazyByteString2 . putF) x+        , bench (name ++ " Builder") $+            whnf (L.length . B.toLazyByteString . builderF) x+        ]++word8s :: [Word8]+word8s = take 100000 $ cycle [0..]+{-# NOINLINE word8s #-}++------------------------------------------------------------------------------+-- The Put type+------------------------------------------------------------------------------++data BufRange = BufRange {-# UNPACK #-} !(Ptr Word8) {-# UNPACK #-} !(Ptr Word8)++newtype Put a = Put {+    unPut :: forall r. (a -> PutStep r) -> PutStep r+  }++data PutSignal a =+    Done {-# UNPACK #-} !(Ptr Word8) a+  | BufferFull+      {-# UNPACK #-} !Int+      {-# UNPACK #-} !(Ptr Word8)+                     !(PutStep a)+  | InsertByteString+      {-# UNPACK #-} !(Ptr Word8) +                     !S.ByteString+                     !(PutStep a)++type PutStep a =  BufRange -> IO (PutSignal a)++instance Monad Put where+  return x = Put $ \k -> k x+  {-# INLINE return #-}+  m >>= f  = Put $ \k -> unPut m (\x -> unPut (f x) k)+  {-# INLINE (>>=) #-}+  m >>  n  = Put $ \k -> unPut m (\_ -> unPut n k)+  {-# INLINE (>>) #-}++------------------------------------------------------------------------------+-- The Builder type with equal signals as the Put type+------------------------------------------------------------------------------++newtype Builder = Builder (forall r. PutStep r -> PutStep r)++instance Monoid Builder where+  mempty = Builder id+  {-# INLINE mempty #-}+  (Builder b1) `mappend` (Builder b2) = Builder $ b1 . b2+  {-# INLINE mappend #-}+  mconcat = foldr mappend mempty+  {-# INLINE mconcat #-}++fromBuilder :: Builder -> Put ()+fromBuilder (Builder build) = Put $ \k -> build (k ())++toBuilder :: Put () -> Builder+toBuilder (Put put) = Builder $ \k -> put (\_ -> k)++fromWrite :: Write -> Builder+fromWrite (Write size io) =+    Builder step+  where+    step k (BufRange pf pe)+      | pf `plusPtr` size <= pe = do+          io pf+          let !br' = BufRange (pf `plusPtr` size) pe+          k br'+      | otherwise = return $ BufferFull size pf (step k)+{-# INLINE fromWrite #-}++fromWriteSingleton :: (a -> Write) -> a -> Builder+fromWriteSingleton write = +    mkPut+  where+    mkPut x = Builder step+      where+        step k (BufRange pf pe)+          | pf `plusPtr` size <= pe = do+              io pf+              let !br' = BufRange (pf `plusPtr` size) pe+              k br'+          | otherwise               = return $ BufferFull size pf (step k)+          where+            Write size io = write x+{-# INLINE fromWriteSingleton #-}++fromWord8 :: Word8 -> Builder+fromWord8 = fromWriteSingleton writeWord8+++------------------------------------------------------------------------------+-- Implementations+------------------------------------------------------------------------------++putWord8 :: Word8 -> Put ()+putWord8 = putWriteSingleton writeWord8++putWrite :: Write -> Put ()+putWrite (Write size io) =+    Put step+  where+    step k (BufRange pf pe)+      | pf `plusPtr` size <= pe = do+          io pf+          let !br' = BufRange (pf `plusPtr` size) pe+          k () br'+      | otherwise = return $ BufferFull size pf (step k)+{-# INLINE putWrite #-}++putWriteSingleton :: (a -> Write) -> a -> Put ()+putWriteSingleton write = +    mkPut+  where+    mkPut x = Put step+      where+        step k (BufRange pf pe)+          | pf `plusPtr` size <= pe = do+              io pf+              let !br' = BufRange (pf `plusPtr` size) pe+              k () br'+          | otherwise               = return $ BufferFull size pf (step k)+          where+            Write size io = write x+{-# INLINE putWriteSingleton #-}++putBuilder :: B.Builder -> Put ()+putBuilder (B.Builder b) = +    Put step+  where+    finalStep _ pf = return $ B.Done pf++    step k = go (b finalStep)+      where+        go buildStep (BufRange pf pe) = do+          signal <- buildStep pf pe+          case signal of+            B.Done pf' -> do+              let !br' = BufRange pf' pe+              k () br'+            B.BufferFull minSize pf' nextBuildStep -> +              return $ BufferFull minSize pf' (go nextBuildStep)+            B.ModifyChunks _ _ _ -> +              error "putBuilder: ModifyChunks not implemented"++{-+  m >>= f  = GetC $ \done empty pe ->+      runGetC m (\pr' x -> runGetC (f x) done empty pe pr') +                (\m' -> empty (m' >>= f))+                pe+++newtype GetC r a = GetC {+    runGetC ::+      (Ptr Word8 -> a -> IO r) ->   -- done+      (GetC r a -> IO r     )  ->   -- empty buffer+      Ptr Word8                ->   -- end of buffer+      Ptr Word8                ->   -- next byte to read+      IO r+  }++instance Functor (GetC r) where+  fmap f g = GetC $ \done empty ->+      runGetC g (\pr' x -> done pr' (f x)) +                (\g'    -> empty (fmap f g'))++instance Monad (GetC r) where+  return x = GetC $ \done _ _ pr -> done pr x+  m >>= f  = GetC $ \done empty pe ->+      runGetC m (\pr' x -> runGetC (f x) done empty pe pr') +                (\m' -> empty (m' >>= f))+                pe++-}++------------------------------------------------------------------------------+-- Internal global constants.+------------------------------------------------------------------------------++-- | Default size (~32kb) for the buffer that becomes a chunk of the output+-- stream once it is filled.+--+defaultBufferSize :: Int+defaultBufferSize = 32 * 1024 - overhead -- Copied from Data.ByteString.Lazy.+    where overhead = 2 * sizeOf (undefined :: Int)++-- | The minimal length (~4kb) a buffer must have before filling it and+-- outputting it as a chunk of the output stream. +--+-- This size determines when a buffer is spilled after a 'flush' or a direct+-- bytestring insertion. It is also the size of the first chunk generated by+-- 'toLazyByteString'.+defaultMinimalBufferSize :: Int+defaultMinimalBufferSize = 4 * 1024 - overhead+    where overhead = 2 * sizeOf (undefined :: Int)++-- | The default length (64) for the first buffer to be allocated when+-- converting a 'Builder' to a lazy bytestring. +--+-- See 'toLazyByteStringWith' for further explanation.+defaultFirstBufferSize :: Int+defaultFirstBufferSize = 64++-- | The maximal number of bytes for that copying is cheaper than direct+-- insertion into the output stream. This takes into account the fragmentation+-- that may occur in the output buffer due to the early 'flush' implied by the+-- direct bytestring insertion.+--+-- @'defaultMaximalCopySize' = 2 * 'defaultMinimalBufferSize'@+--+defaultMaximalCopySize :: Int+defaultMaximalCopySize = 2 * defaultMinimalBufferSize++------------------------------------------------------------------------------+-- Flushing and running a Builder+------------------------------------------------------------------------------+++-- | Output all data written in the current buffer and start a new chunk.+--+-- The use uf this function depends on how the resulting bytestrings are+-- consumed. 'flush' is possibly not very useful in non-interactive scenarios.+-- However, it is kept for compatibility with the builder provided by+-- Data.Binary.Builder.+--+-- When using 'toLazyByteString' to extract a lazy 'L.ByteString' from a+-- 'Builder', this means that a new chunk will be started in the resulting lazy+-- 'L.ByteString'. The remaining part of the buffer is spilled, if the+-- reamining free space is smaller than the minimal desired buffer size.+--+{-+flush :: Builder+flush = Builder $ \k pf _ -> return $ ModifyChunks pf id k+-}++-- | Run a 'Builder' with the given buffer sizes.+--+-- Use this function for integrating the 'Builder' type with other libraries+-- that generate lazy bytestrings.+--+-- Note that the builders should guarantee that on average the desired chunk+-- size is attained. Builders may decide to start a new buffer and not+-- completely fill the existing buffer, if this is faster. However, they should+-- not spill too much of the buffer, if they cannot compensate for it.+--+-- A call @toLazyByteStringWith bufSize minBufSize firstBufSize@ will generate+-- a lazy bytestring according to the following strategy. First, we allocate+-- a buffer of size @firstBufSize@ and start filling it. If it overflows, we+-- allocate a buffer of size @minBufSize@ and copy the first buffer to it in+-- order to avoid generating a too small chunk. Finally, every next buffer will+-- be of size @bufSize@. This, slow startup strategy is required to achieve+-- good speed for short (<200 bytes) resulting bytestrings, as for them the+-- allocation cost is of a large buffer cannot be compensated. Moreover, this+-- strategy also allows us to avoid spilling too much memory for short+-- resulting bytestrings.+--+-- Note that setting @firstBufSize >= minBufSize@ implies that the first buffer+-- is no longer copied but allocated and filled directly. Hence, setting+-- @firstBufSize = bufSize@ means that all chunks will use an underlying buffer+-- of size @bufSize@. This is recommended, if you know that you always output+-- more than @minBufSize@ bytes.+toLazyByteStringWith +    :: Int           -- ^ Buffer size (upper-bounds the resulting chunk size).+    -> Int           -- ^ Minimal free buffer space for continuing filling+                     -- the same buffer after a 'flush' or a direct bytestring+                     -- insertion. This corresponds to the minimal desired+                     -- chunk size.+    -> Int           -- ^ Size of the first buffer to be used and copied for+                     -- larger resulting sequences+    -> Put a       -- ^ Builder to run.+    -> L.ByteString  -- ^ Lazy bytestring to output after the builder is+                     -- finished.+    -> L.ByteString  -- ^ Resulting lazy bytestring+toLazyByteStringWith bufSize minBufSize firstBufSize (Put b) k = +    inlinePerformIO $ fillFirstBuffer (b finalStep)+  where+    finalStep _ (BufRange pf _) = return $ Done pf undefined+    -- fill a first very small buffer, if we need more space then copy it+    -- to the new buffer of size 'minBufSize'. This way we don't pay the+    -- allocation cost of the big 'bufSize' buffer, when outputting only+    -- small sequences.+    fillFirstBuffer !step0+      | minBufSize <= firstBufSize = fillNewBuffer firstBufSize step0+      | otherwise                  = do+          fpbuf <- S.mallocByteString firstBufSize+          withForeignPtr fpbuf $ \pf -> do+              let !br      = BufRange pf (pf `plusPtr` firstBufSize)+                  mkbs pf' = S.PS fpbuf 0 (pf' `minusPtr` pf)+                  {-# INLINE mkbs #-}+              next <- step0 br+              case next of+                  Done pf' _+                    | pf' == pf -> return k+                    | otherwise -> return $ L.Chunk (mkbs pf') k++                  BufferFull newSize pf' nextStep  -> do+                      let !l  = pf' `minusPtr` pf+                      fillNewBuffer (max (l + newSize) minBufSize) $+                          \(BufRange pfNew peNew) -> do+                              copyBytes pfNew pf l+                              let !brNew = BufRange (pfNew `plusPtr` l) peNew+                              nextStep brNew+                      +                  InsertByteString _ _ _ -> error "not yet implemented"+                  {-+                  ModifyChunks pf' bsk nextStep( +                      | pf' == pf ->+                          return $ bsk (inlinePerformIO $ fillNewBuffer bufSize nextStep)+                      | otherwise ->+                          return $ L.Chunk (mkbs pf')+                              (bsk (inlinePerformIO $ fillNewBuffer bufSize nextStep))+                  -}+                    +    -- allocate and fill a new buffer+    fillNewBuffer !size !step0 = do+        fpbuf <- S.mallocByteString size+        withForeignPtr fpbuf $ fillBuffer fpbuf+      where+        fillBuffer fpbuf !pbuf = fill pbuf step0+          where+            !pe = pbuf `plusPtr` size+            fill !pf !step = do+                let !br = BufRange pf pe+                next <- step br+                let mkbs pf' = S.PS fpbuf (pf `minusPtr` pbuf) (pf' `minusPtr` pf)+                    {-# INLINE mkbs #-}+                case next of+                    Done pf' _+                      | pf' == pf -> return k+                      | otherwise -> return $ L.Chunk (mkbs pf') k++                    BufferFull newSize pf' nextStep ->+                        return $ L.Chunk (mkbs pf')+                            (inlinePerformIO $ +                                fillNewBuffer (max newSize bufSize) nextStep)+                        +                    InsertByteString _ _ _ -> error "not yet implemented2"+                    {-+                    ModifyChunks  pf' bsk nextStep+                      | pf' == pf                      ->+                          return $ bsk (inlinePerformIO $ fill pf' nextStep)+                      | minBufSize < pe `minusPtr` pf' ->+                          return $ L.Chunk (mkbs pf')+                              (bsk (inlinePerformIO $ fill pf' nextStep))+                      | otherwise                      ->+                          return $ L.Chunk (mkbs pf')+                              (bsk (inlinePerformIO $ fillNewBuffer bufSize nextStep))+                    -}+++-- | Extract the lazy 'L.ByteString' from the builder by running it with default+-- buffer sizes. Use this function, if you do not have any special+-- considerations with respect to buffer sizes.+--+-- @ 'toLazyByteString' b = 'toLazyByteStringWith' 'defaultBufferSize' 'defaultMinimalBufferSize' 'defaultFirstBufferSize' b L.empty@+--+-- Note that @'toLazyByteString'@ is a 'Monoid' homomorphism.+--+-- > toLazyByteString mempty          == mempty+-- > toLazyByteString (x `mappend` y) == toLazyByteString x `mappend` toLazyByteString y+--+-- However, in the second equation, the left-hand-side is generally faster to+-- execute.+--+toLazyByteString :: Put a -> L.ByteString+toLazyByteString b = toLazyByteStringWith +    defaultBufferSize defaultMinimalBufferSize defaultFirstBufferSize b L.empty+{-# INLINE toLazyByteString #-}++------------------------------------------------------------------------------+-- Builder Enumeration+------------------------------------------------------------------------------++data BuildStream a = +         BuildChunk  S.ByteString (IO (BuildStream a))+       | BuildYield+           a            +           (forall b. Bool -> +                      Either (Maybe S.ByteString) (Put b -> IO (BuildStream b)))++enumPut :: Int -> Put a -> IO (BuildStream a)+enumPut bufSize (Put put0) =+    fillBuffer bufSize (put0 finalStep)+  where+    finalStep :: forall b. b -> PutStep b+    finalStep x (BufRange op _) = return $ Done op x++    fillBuffer :: forall b. Int -> PutStep b -> IO (BuildStream b)+    fillBuffer size step = do+        fpbuf <- S.mallocByteString bufSize +        let !pbuf = unsafeForeignPtrToPtr fpbuf+                  -- safe due to later reference of fpbuf+                  -- BETTER than withForeignPtr, as we lose a tail call otherwise+            !br = BufRange pbuf (pbuf `plusPtr` size)+        fillStep fpbuf br step++    fillPut :: ForeignPtr Word8 -> BufRange -> +               Bool -> Either (Maybe S.ByteString) (Put b -> IO (BuildStream b))+    fillPut !fpbuf !(BufRange op _) False +      | pbuf == op = Left Nothing+      | otherwise  = Left $ Just $+          S.PS fpbuf 0 (op `minusPtr` pbuf)+      where+        pbuf = unsafeForeignPtrToPtr fpbuf+        {-# INLINE pbuf #-}+    fillPut !fpbuf !br True =+        Right $ \(Put put) -> fillStep fpbuf br (put finalStep)++    fillStep :: forall b. ForeignPtr Word8 -> BufRange -> PutStep b -> IO (BuildStream b)+    fillStep !fpbuf !br@(BufRange _ ope) step = do+        let pbuf = unsafeForeignPtrToPtr fpbuf+            {-# INLINE pbuf #-}+        signal <- step br+        case signal of+            Done op' x -> do      -- builder completed, buffer partially filled+                let !br' = BufRange op' ope+                return $ BuildYield x (fillPut fpbuf br')++            BufferFull minSize op' nextStep  +              | pbuf == op' -> do -- nothing written, larger buffer required+                  fillBuffer (max bufSize minSize) nextStep+              | otherwise   -> do -- some bytes written, new buffer required+                  return $ BuildChunk +                    (S.PS fpbuf 0 (op' `minusPtr` pbuf))+                    (fillBuffer (max bufSize minSize) nextStep)++            InsertByteString op' bs nextStep+              | S.null bs -> do   -- empty bytestrings are ignored+                  let !br' = BufRange op' ope+                  fillStep fpbuf br' nextStep+              | pbuf == op' -> do -- no bytes written: just insert bytestring+                  return $ BuildChunk bs (fillBuffer bufSize nextStep)+              | otherwise -> do   -- bytes written, insert buffer and bytestring+                  return $ BuildChunk (S.PS fpbuf 0 (op' `minusPtr` pbuf))+                    (return $ BuildChunk bs (fillBuffer bufSize nextStep))+++toLazyByteString' :: Put () -> L.ByteString+toLazyByteString' put = +    inlinePerformIO (consume `fmap` enumPut defaultBufferSize put)+  where+    consume :: BuildStream () -> L.ByteString+    consume (BuildYield _ f) = +        case f False of+          Left Nothing   -> L.Empty+          Left (Just bs) -> L.Chunk bs L.Empty+          Right _        -> error "toLazyByteString': enumPut violated postcondition"+    consume (BuildChunk bs ioStream) =+        L.Chunk bs $ inlinePerformIO (consume `fmap` ioStream)+        +++{-+                    BufferFull minSize pf' nextStep  -> do+                        io $ S.PS fpbuf 0 (pf' `minusPtr` pf)+                        fillBuffer (max bufSize minSize) nextStep+                        +                    ModifyChunks pf' bsk nextStep  -> do+                        io $ S.PS fpbuf 0 (pf' `minusPtr` pf)+                        L.foldrChunks (\bs -> (io bs >>)) (return ()) (bsk L.empty)+                        fillBuffer bufSize nextStep+-}++------------------------------------------------------------------------------+-- More explicit implementation of running builders+------------------------------------------------------------------------------+++data Buffer = Buffer {-# UNPACK #-} !(ForeignPtr Word8) -- underlying pinned array+                     {-# UNPACK #-} !(Ptr Word8)        -- beginning of slice+                     {-# UNPACK #-} !(Ptr Word8)        -- next free byte+                     {-# UNPACK #-} !(Ptr Word8)        -- first byte after buffer++allocBuffer :: Int -> IO Buffer+allocBuffer size = do+    fpbuf <- S.mallocByteString size+    let !pbuf = unsafeForeignPtrToPtr fpbuf+    return $! Buffer fpbuf pbuf pbuf (pbuf `plusPtr` size)++unsafeFreezeBuffer :: Buffer -> S.ByteString+unsafeFreezeBuffer (Buffer fpbuf p0 op _) = +    S.PS fpbuf 0 (op `minusPtr` p0)++unsafeFreezeNonEmptyBuffer :: Buffer -> Maybe S.ByteString+unsafeFreezeNonEmptyBuffer (Buffer fpbuf p0 op _) +  | p0 == op  = Nothing+  | otherwise = Just $ S.PS fpbuf 0 (op `minusPtr` p0)++nextSlice :: Int -> Buffer -> Maybe Buffer+nextSlice minSize (Buffer fpbuf _ op ope)+  | ope `minusPtr` op <= minSize = Nothing+  | otherwise                    = Just (Buffer fpbuf op op ope)++runPut :: Monad m +       => (IO (PutSignal a) -> m (PutSignal a)) -- lifting of buildsteps+       -> (Int -> Buffer -> m Buffer) -- output function for a guaranteedly non-empty buffer, the returned buffer will be filled next+       -> (S.ByteString -> m ())    -- output function for guaranteedly non-empty bytestrings, that are inserted directly into the stream+       -> Put a                     -- put to execute+       -> Buffer                    -- initial buffer to be used+       -> m (a, Buffer)             -- result of put and remaining buffer+runPut liftIO outputBuf outputBS (Put put) =+    runStep (put finalStep)+  where+    finalStep x !(BufRange op _) = return $ Done op x++    runStep step buf@(Buffer fpbuf p0 op ope) = do+        let !br = BufRange op ope+        signal <- liftIO $ step br+        case signal of +            Done op' x ->         -- put completed, buffer partially runSteped+                return (x, Buffer fpbuf p0 op' ope)++            BufferFull minSize op' nextStep -> do+                buf' <- outputBuf minSize (Buffer fpbuf p0 op' ope)+                runStep nextStep buf'++            InsertByteString op' bs nextStep+              | S.null bs ->   -- flushing of buffer required+                  outputBuf 1 (Buffer fpbuf p0 op' ope) >>= runStep nextStep+              | p0 == op' -> do -- no bytes written: just insert bytestring+                  outputBS bs+                  runStep nextStep buf+              | otherwise -> do   -- bytes written, insert buffer and bytestring+                  buf' <- outputBuf 1 (Buffer fpbuf p0 op' ope)+                  outputBS bs+                  runStep nextStep buf'+{-# INLINE runPut #-}+              +-- | A monad for lazily composing lazy bytestrings using continuations.+newtype LBSM a = LBSM { unLBSM :: (a, L.ByteString -> L.ByteString) }++instance Monad LBSM where+    return x                       = LBSM (x, id)+    (LBSM (x,k)) >>= f             = let LBSM (x',k') = f x in LBSM (x', k . k')+    (LBSM (_,k)) >> (LBSM (x',k')) = LBSM (x', k . k')++-- | Execute a put and return the written buffers as the chunks of a lazy+-- bytestring.+toLazyByteString2 :: Put a -> L.ByteString+toLazyByteString2 put = +    k (bufToLBSCont (snd result) L.empty)+  where+    -- initial buffer+    buf0 = inlinePerformIO $ allocBuffer defaultBufferSize+    -- run put, but don't force result => we're lazy enough+    LBSM (result, k) = runPut liftIO outputBuf outputBS put buf0+    -- convert a buffer to a lazy bytestring continuation+    bufToLBSCont = maybe id L.Chunk . unsafeFreezeNonEmptyBuffer+    -- lifting an io putsignal to a lazy bytestring monad+    liftIO io = LBSM (inlinePerformIO io, id)+    -- add buffer as a chunk prepare allocation of new one+    outputBuf minSize buf = LBSM+        ( inlinePerformIO $ allocBuffer (max minSize defaultBufferSize)+        , bufToLBSCont buf )+    -- add bytestring directly as a chunk; exploits postcondition of runPut+    -- that bytestrings are non-empty+    outputBS bs = LBSM ((), L.Chunk bs)++-- | A Builder that traces a message+traceBuilder :: String -> Builder +traceBuilder msg = Builder $ \k br@(BufRange op ope) -> do+    putStrLn $ "traceBuilder " ++ show (op, ope) ++ ": " ++ msg+    k br++flushBuilder :: Builder+flushBuilder = Builder $ \k (BufRange op _) -> do+    return $ InsertByteString op S.empty k++test2 :: Word8 -> [S.ByteString]+test2 x = L.toChunks $ toLazyByteString2 $ fromBuilder $ mconcat+  [ traceBuilder "before flush" +  , fromWord8 48+  , flushBuilder+  , flushBuilder+  , traceBuilder "after flush"+  , fromWord8 x+  ]+
+ benchmarks/LazyByteString.hs view
@@ -0,0 +1,410 @@+{-# LANGUAGE BangPatterns #-}+-- |+-- Module      : LazyByteString+-- Copyright   : (c) 2010 Simon Meier+-- License     : BSD3-style (see LICENSE)+-- +-- Maintainer  : Simon Meier <iridcode@gmail.com>+-- Stability   : experimental+-- Portability : tested on GHC only+--+-- Benchmarking of alternative implementations of functions in+-- Data.ByteString.Lazy that construct lazy bytestrings and cannot be+-- implemented with slicing only.+module LazyByteString where -- (main)  where++import Data.Word+import Data.Monoid +import Data.List ++import Criterion.Main++import Foreign +import qualified Data.ByteString               as S+import qualified Data.ByteString.Internal      as S+import qualified Data.ByteString.Lazy          as L+import qualified Data.ByteString.Lazy.Internal as L++import Blaze.ByteString.Builder.Internal+import Blaze.ByteString.Builder.Write+import Blaze.ByteString.Builder.Word+import Blaze.ByteString.Builder.ByteString++------------------------------------------------------------------------------+-- Benchmarks+------------------------------------------------------------------------------++main :: IO ()+main = do+    let (chunkInfos, benchmarks) = unzip +          [ lazyVsBlaze+              ( "copy"+              , L.copy+              , copyBlaze+              , (\i -> L.drop 13 $ L.take (fromIntegral i) $ L.fromChunks $ repeat $ S.pack [0..])+              , n)+          , lazyVsBlaze+              ( "filter ((==0) . (`mod` 3))"+              , L.filter ((==0) . (`mod` 3))+              , filterBlaze ((==0) . (`mod` 3))+              , (\i -> L.drop 13 $ L.pack $ take i $ cycle [0..])+              , n)+          , lazyVsBlaze+              ( "map (+1)"+              , L.map (+1)+              , mapBlaze (+1)+              , (\i -> L.drop 13 $ L.pack $ take i $ cycle [0..])+              , n)+          , lazyVsBlaze+              ( "concatMap (replicate 10)"+              , L.concatMap (L.replicate 10)+              , toLazyByteString . concatMapBuilder (fromReplicateWord8 10)+              , (\i -> L.pack $ take i $ cycle [0..])+              , n `div` 10 )+          , lazyVsBlaze +              ( "unfoldr countToZero"+              , L.unfoldr    countToZero+              , unfoldrBlaze countToZero+              , id+              , n )+          ]+    sequence_ (intersperse (putStrLn "") chunkInfos)+    putStrLn ""+    defaultMain benchmarks+  where+    n :: Int+    n = 100000++lazyVsBlaze :: (String, a -> L.ByteString, a -> L.ByteString, Int -> a, Int)+        -> (IO (), Benchmark)+lazyVsBlaze (cmpName, lazy, blaze, prep, n) =+    ( do putStrLn $ cmpName ++ ": " ++ checkResults+         showChunksize implLazy  lazy+         showChunksize implBlaze blaze+    , bgroup cmpName +        [ mkBench implLazy  lazy+        , mkBench implBlaze blaze+        ]+    )+  where+    implLazy  = "bytestring"+    implBlaze = "blaze-builder"+    x = prep n++    nInfo = "for n = " ++ show n+    checkResults +      | lazy x == blaze x = "implementations agree " ++ nInfo+      | otherwise         = unlines [ "ERROR: IMPLEMENTATIONS DISAGREE " ++ nInfo+                                    , implLazy ++ ": " ++ show (lazy x)+                                    , implBlaze ++ ": " ++ show (blaze x)+                                    ]++    showChunksize implName impl = do+      let bs = impl x+          cs = map S.length $ L.toChunks bs+      putStrLn $ "  " ++ implName ++ ": "+      putStrLn $ "    chunks sizes:    " ++ show cs+      putStrLn $ "    avg. chunk size: " ++ +        show ((fromIntegral (sum cs) :: Double) / fromIntegral (length cs))++    mkBench implName impl = bench implName $ whnf (L.length . impl) x+++------------------------------------------------------------------------------+-- Alternative implementations+------------------------------------------------------------------------------++-- Unfolding+------------++{-+-- | /O(n)/ The 'unfoldr' function is analogous to the List \'unfoldr\'.+-- 'unfoldr' builds a ByteString from a seed value.  The function takes+-- the element and returns 'Nothing' if it is done producing the+-- ByteString or returns 'Just' @(a,b)@, in which case, @a@ is a+-- prepending to the ByteString and @b@ is used as the next element in a+-- recursive call.+unfoldr :: (a -> Maybe (Word8, a)) -> a -> ByteString+unfoldr f s0 = unfoldChunk 32 s0+  where unfoldChunk n s =+          case S.unfoldrN n f s of+            (c, Nothing)+              | S.null c  -> Empty+              | otherwise -> Chunk c Empty+            (c, Just s')  -> Chunk c (unfoldChunk (n*2) s')+-}++countToZero :: Int -> Maybe (Word8, Int)+countToZero 0 = Nothing+countToZero i = Just (fromIntegral i, i - 1)++unfoldrBlaze :: (a -> Maybe (Word8, a)) -> a -> L.ByteString+unfoldrBlaze f x = toLazyByteString $ fromWriteUnfoldr writeWord8 f x++fromWriteUnfoldr :: (b -> Write) -> (a -> Maybe (b, a)) -> a -> Builder+fromWriteUnfoldr write = +    makeBuilder+  where+    makeBuilder f x0 = Builder $ step x0+      where+        step x1 !k = fill x1+          where+            fill x !pf0 !pe0 = go (f x) pf0+              where+                go !Nothing        !pf = k pf pe0+                go !(Just (y, x')) !pf+                  | pf `plusPtr` size <= pe0 = do+                      io pf+                      go (f x') (pf `plusPtr` size)+                  | otherwise = return $ BufferFull size pf $ \pfNew peNew -> do +                      io pfNew+                      fill x' (pfNew `plusPtr` size) peNew+                  where+                    !(Write size io) = write y+{-# INLINE fromWriteUnfoldr #-}++-- Filtering and mapping+------------------------++test i = +    ((L.filter ((==0) . (`mod` 3)) $ x) ,+     (filterBlaze ((==0) . (`mod` 3)) $ x))+  where+    x = L.pack $ take i $ cycle [0..]++filterBlaze :: (Word8 -> Bool) -> L.ByteString -> L.ByteString+filterBlaze f = toLazyByteString . filterLazyByteString f+{-# INLINE filterBlaze #-}++mapBlaze :: (Word8 -> Word8) -> L.ByteString -> L.ByteString+mapBlaze f = toLazyByteString . mapLazyByteString f+{-# INLINE mapBlaze #-}++filterByteString :: (Word8 -> Bool) -> S.ByteString -> Builder+filterByteString p = mapFilterMapByteString id p id+{-# INLINE filterByteString #-}++filterLazyByteString :: (Word8 -> Bool) -> L.ByteString -> Builder+filterLazyByteString p = mapFilterMapLazyByteString id p id+{-# INLINE filterLazyByteString #-}++mapByteString :: (Word8 -> Word8) -> S.ByteString -> Builder+mapByteString f = mapFilterMapByteString f (const True) id+{-# INLINE mapByteString #-}++mapLazyByteString :: (Word8 -> Word8) -> L.ByteString -> Builder+mapLazyByteString f = mapFilterMapLazyByteString f (const True) id+{-# INLINE mapLazyByteString #-}++mapFilterMapByteString :: (Word8 -> Word8) -> (Word8 -> Bool) -> (Word8 -> Word8) +                       -> S.ByteString -> Builder+mapFilterMapByteString f p g = +    \bs -> Builder $ step bs+  where+    step (S.PS ifp ioff isize) !k = +        goBS (unsafeForeignPtrToPtr ifp `plusPtr` ioff)+      where+        !ipe = unsafeForeignPtrToPtr ifp `plusPtr` (ioff + isize)+        goBS !ip0 !op0 !ope+          | ip0 >= ipe = do touchForeignPtr ifp -- input buffer consumed+                            k op0 ope+          | op0 < ope  = goPartial (ip0 `plusPtr` min outRemaining inpRemaining)+          | otherwise  = return $ BufferFull 1 op0 (goBS ip0) +          where+            outRemaining = ope `minusPtr` op0+            inpRemaining = ipe `minusPtr` ip0+            goPartial !ipeTmp = go ip0 op0+              where+                go !ip !op+                  | ip < ipeTmp = do+                      w <- peek ip+                      let w' = g w+                      if p w'+                        then poke op (f w') >> go (ip `plusPtr` 1) (op `plusPtr` 1)+                        else                   go (ip `plusPtr` 1) op+                  | otherwise =+                      goBS ip op ope+{-# INLINE mapFilterMapByteString #-}++mapFilterMapLazyByteString :: (Word8 -> Word8) -> (Word8 -> Bool) -> (Word8 -> Word8) +                           -> L.ByteString -> Builder+mapFilterMapLazyByteString f p g = +    L.foldrChunks (\c b -> mapFilterMapByteString f p g c `mappend` b) mempty+{-# INLINE mapFilterMapLazyByteString #-}+++-- Concatenation and replication+--------------------------------++{-+-- | Map a function over a 'ByteString' and concatenate the results+concatMap :: (Word8 -> ByteString) -> ByteString -> ByteString+concatMap _ Empty        = Empty+concatMap f (Chunk c0 cs0) = to c0 cs0+  where+    go :: ByteString -> P.ByteString -> ByteString -> ByteString+    go Empty        c' cs' = to c' cs'+    go (Chunk c cs) c' cs' = Chunk c (go cs c' cs')++    to :: P.ByteString -> ByteString -> ByteString+    to c cs | S.null c  = case cs of+        Empty          -> Empty+        (Chunk c' cs') -> to c' cs'+            | otherwise = go (f (S.unsafeHead c)) (S.unsafeTail c) cs+-}++fromWriteReplicated :: (a -> Write) -> Int -> a -> Builder+fromWriteReplicated write = +    makeBuilder+  where+    makeBuilder !n0 x = Builder $ step +      where+        Write size io = write x+        step !k = fill n0+          where+            fill !n1 !pf0 !pe0 = go n1 pf0+              where+                go 0 !pf = k pf pe0+                go n !pf+                  | pf `plusPtr` size <= pe0 = do+                      io pf+                      go (n-1) (pf `plusPtr` size)+                  | otherwise = return $ BufferFull size pf $ \pfNew peNew -> do +                      io pfNew+                      fill (n-1) (pfNew `plusPtr` size) peNew+{-# INLINE fromWriteReplicated #-}++-- FIXME: Output repeated bytestrings for large replications.+fromReplicateWord8 :: Int -> Word8 -> Builder+fromReplicateWord8 !n0 x = +    Builder $ step+  where+    step !k = fill n0+      where+        fill !n !pf !pe+          | n <= 0    = k pf pe+          | pf' <= pe = do+              _ <- S.memset pf x (fromIntegral n) -- FIXME: This conversion looses information for 64 bit systems.+              k pf' pe+          | otherwise  = do+              let !l = pe `minusPtr` pf+              _ <- S.memset pf x (fromIntegral l) -- FIXME: This conversion looses information for 64 bit systems.+              return $ BufferFull 1 pe $ fill (n - l)+          where+            pf' = pf `plusPtr` n+{-# INLINE fromReplicateWord8 #-}+++{-# RULES "fromWriteReplicated/writeWord8"+      fromWriteReplicated writeWord8 = fromReplicateWord8+ #-}+++concatMapBuilder :: (Word8 -> Builder) -> L.ByteString -> Builder+concatMapBuilder f = L.foldr (\w b -> f w `mappend` b) mempty+{-# INLINE concatMapBuilder #-}++concatMapBlaze :: (Word8 -> L.ByteString) -> L.ByteString -> L.ByteString+concatMapBlaze f = toLazyByteString . concatMapBuilder (fromLazyByteString . f)+++-- Interspersing+----------------++--+-- not sure if it Builder version is needed, as chunks get only bigger. We+-- would need it however, if we used a Builder to ensure latency guarantees; i.e.,+-- if we use a builder to ensure a bound on the maximal size of chunks.+--++{-+-- | The 'intersperse' function takes a 'Word8' and a 'ByteString' and+-- \`intersperses\' that byte between the elements of the 'ByteString'.+-- It is analogous to the intersperse function on Lists.+intersperse :: Word8 -> ByteString -> ByteString+intersperse _ Empty        = Empty+intersperse w (Chunk c cs) = Chunk (S.intersperse w c)+                                   (foldrChunks (Chunk . intersperse') Empty cs)+  where intersperse' :: P.ByteString -> P.ByteString+        intersperse' (S.PS fp o l) =+          S.unsafeCreate (2*l) $ \p' -> withForeignPtr fp $ \p -> do+            poke p' w+            S.c_intersperse (p' `plusPtr` 1) (p `plusPtr` o) (fromIntegral l) w+-}+{-+intersperseBlaze :: Word8         -- ^ Byte to intersperse.+                 -> L.ByteString  -- ^ Lazy 'L.ByteString' to be "spread".+                 -> Builder       -- ^ Resulting 'Builder'.+intersperseBlaze w lbs0 = +    Builder $ step lbs0+  where+    step lbs1 k = goChunk lbs1+      where+        goChunk L.Empty                         pf0 pe0 = k pf0 pe0+        goChunk (L.Chunk (S.PS fpi oi li) lbs') pf0 pe0 = do+            go+            touch+          where+            go +              where+                !pf' = pf `plusPtr` +                +            +        goChunk !L.Empty                !pf = k pf pe0+        goChunk !lbs@(L.Chunk bs' lbs') !pf+          | pf' <= pe0 = do+              withForeignPtr fpbuf $ \pbuf -> +                  copyBytes pf (pbuf `plusPtr` offset) size+              go lbs' pf'++          | otherwise  = return $ BufferFull size pf (step lbs k)+          where+            !pf' = pf `plusPtr` +            !(fpbuf, offset, size) = S.toForeignPtr bs'+{-# INLINE intersperseBlaze #-}++-}+++-- Packing+----------++packBlaze :: [Word8] -> L.ByteString+packBlaze = toLazyByteString . fromWrite1List writeWord8+++-- Reverse+----------+++-- Transpose+------------+++-- scanl, scanl1, scanr, scanr1+-------------------------------+++-- mapAccumL, mapAccumR+-----------------------+++-- partition+------------++-- unzip+--------+++-- copy+-------++-- FIXME: Implement wrapping+copyBlaze :: L.ByteString -> L.ByteString+copyBlaze = toLazyByteString . copyLazyByteString+++-- ?? packCString, packCStringLen+---------------------------------++
+ benchmarks/StrictIO.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE BangPatterns #-}+-- | Demonstrate the problem with IO not allowing for unlifted types.+--+--   TODO: Not yet finished.+module StrictIO where++++loop :: Int -> Int -> IO ()+loop !i !c+    | i == 1    = print c+    | otherwise = do+        !i' <- subcases+        print i'+        loop i' (c+1)+  where+    subcases+      | i `mod` 2 == 0 = do+          print "even"+          return $ i `div` 2+      | otherwise      = do+          print "odd"+          return $ i + 1+    {-# INLINE subcases #-}+++  +
benchmarks/StringAndText.hs view
@@ -42,8 +42,8 @@     , bench "fromStrictTextUnpacked :: StrictText --[Utf8 encoding]--> L.ByteString" $ whnf         (L.length . Blaze.toLazyByteString . Blaze.fromText) benchStrictText      -    , bench "fromStrictTextFolded :: StrictText --[Utf8 encoding]--> L.ByteString" $ whnf-        (L.length . Blaze.toLazyByteString . fromStrictTextFolded) benchStrictText+    -- , bench "fromStrictTextFolded :: StrictText --[Utf8 encoding]--> L.ByteString" $ whnf+        -- (L.length . Blaze.toLazyByteString . fromStrictTextFolded) benchStrictText      , bench "TS.encodeUtf8 :: StrictText --[Utf8 encoding]--> S.ByteString" $ whnf         (TS.encodeUtf8) benchStrictText@@ -51,8 +51,8 @@     , bench "fromLazyTextUnpacked :: LazyText --[Utf8 encoding]--> L.ByteString" $ whnf         (L.length . Blaze.toLazyByteString . Blaze.fromLazyText) benchLazyText -    , bench "fromLazyTextFolded :: LazyText --[Utf8 encoding]--> L.ByteString" $ whnf-        (L.length . Blaze.toLazyByteString . fromLazyTextFolded) benchLazyText+    -- , bench "fromLazyTextFolded :: LazyText --[Utf8 encoding]--> L.ByteString" $ whnf+        -- (L.length . Blaze.toLazyByteString . fromLazyTextFolded) benchLazyText      , bench "TL.encodeUtf8 :: LazyText --[Utf8 encoding]--> L.ByteString" $ whnf         (L.length . TL.encodeUtf8) benchLazyText@@ -83,11 +83,12 @@ benchLazyText = TL.pack benchString {-# NOINLINE benchLazyText #-} +{-  -- | Encode the 'TS.Text' as UTF-8 by folding it and filling the raw buffer -- directly. fromStrictTextFolded :: TS.Text -> Blaze.Builder-fromStrictTextFolded t = Blaze.Builder $ \k -> TS.foldr step k t+fromStrictTextFolded t = Blaze.fromBuildStepCont $ \k -> TS.foldr step k t   where     step c k pf pe       | pf' <= pe = do@@ -95,10 +96,10 @@           k pf' pe  -- here it would be great, if we wouldn't have to pass                     -- around pe: requires a more powerful fold for StrictText.       | otherwise =-          return $ Blaze.BufferFull size pf $ \pfNew peNew -> do-            let pfNew' = pfNew `plusPtr` size+          return $ Blaze.bufferFull size pf $ \(Blaze.BufRange pfNew peNew) -> do+            let !br' = Blaze.BufRange (pfNew `plusPtr` size) peNew             io pfNew-            k pfNew' peNew+            k br'       where         pf' = pf `plusPtr` size         Blaze.Write size io = Blaze.writeChar c@@ -107,7 +108,7 @@ -- | Encode the 'TL.Text' as UTF-8 by folding it and filling the raw buffer -- directly. fromLazyTextFolded :: TL.Text -> Blaze.Builder-fromLazyTextFolded t = Blaze.Builder $ \k -> TL.foldr step k t+fromLazyTextFolded t = Blaze.fromBuildStepContBuilder $ \k -> TL.foldr step k t   where     step c k pf pe       | pf' <= pe = do@@ -115,11 +116,12 @@           k pf' pe  -- here it would be great, if we wouldn't have to pass                     -- around pe: requires a more powerful fold for StrictText.       | otherwise =-          return $ Blaze.BufferFull size pf $ \pfNew peNew -> do-            let pfNew' = pfNew `plusPtr` size+          return $ Blaze.bufferFull size pf $ \(Blaze.BufRange pfNew peNew) -> do+            let !br' = Blaze.BufRange (pfNew `plusPtr` size) peNew             io pfNew-            k pfNew' peNew+            k br'       where         pf' = pf `plusPtr` size         Blaze.Write size io = Blaze.writeChar c {-# INLINE fromLazyTextFolded #-}+-}
+ benchmarks/UnboxedAppend.hs view
@@ -0,0 +1,254 @@+{-# LANGUAGE CPP, BangPatterns, Rank2Types, MagicHash #-}+-- |+-- Module      : UnboxedAppend+-- Copyright   : (c) 2010 Simon Meier+-- License     : BSD3-style (see LICENSE)+-- +-- Maintainer  : Simon Meier <iridcode@gmail.com>+-- Stability   : experimental+-- Portability : tested on GHC only+--+-- Try using unboxed pointers for the continuation calls to make abstract+-- appends go faster.+--+module UnboxedAppend where++import Foreign+import Foreign.UPtr+import Data.Monoid+import qualified Data.ByteString      as S+import qualified Data.ByteString.Lazy as L++#ifdef BYTESTRING_IN_BASE+import Data.ByteString.Base (inlinePerformIO)+import qualified Data.ByteString.Base as S+import qualified Data.ByteString.Lazy.Base as L -- FIXME: is this the right module for access to 'Chunks'?+#else+import Data.ByteString.Internal (inlinePerformIO)+import qualified Data.ByteString.Internal as S+import qualified Data.ByteString.Lazy.Internal as L+#endif++import qualified Blaze.ByteString.Builder.Internal as B+import Blaze.ByteString.Builder.Write (Write(..))+import qualified Blaze.ByteString.Builder.Word     as B+import Blaze.ByteString.Builder.Word (writeWord8)++import Criterion.Main++------------------------------------------------------------------------------+-- Benchmarks+------------------------------------------------------------------------------++main :: IO ()+main = defaultMain $ concat+    [ benchmark "mconcat . map fromWord8"+        myfromWord8s+        yourfromWord8s+        word8s+    ]+  where+    benchmark name putF builderF x =+        [ bench (name ++ " Put") $+            whnf (L.length . toLazyByteString2 . putF) x+        , bench (name ++ " Builder") $+            whnf (L.length . B.toLazyByteString . builderF) x+        ]++word8s :: [Word8]+word8s = take 100000 $ cycle [0..]+{-# NOINLINE word8s #-}++myfromWord8s :: [Word8] -> Put ()+myfromWord8s = putBuilder . mconcat . map fromWord8+{-# NOINLINE myfromWord8s #-}++yourfromWord8s :: [Word8] -> B.Builder+yourfromWord8s = mconcat . map B.fromWord8+{-# NOINLINE yourfromWord8s #-}+++------------------------------------------------------------------------------+-- The Put type+------------------------------------------------------------------------------++data BufRange = BufRange {-# UNPACK #-} !(Ptr Word8) {-# UNPACK #-} !(Ptr Word8)++newtype Put a = Put {+    unPut :: forall r. (a -> PutStep r) -> PutStep r+  }++data PutSignal a =+    Done {-# UNPACK #-} !(Ptr Word8) a+  | BufferFull+      {-# UNPACK #-} !Int+      {-# UNPACK #-} !(Ptr Word8)+                     !(PutStep a)+  | InsertByteString+      {-# UNPACK #-} !(Ptr Word8) +                     !S.ByteString+                     !(PutStep a)++type PutStep a =  UPtr -> UPtr -> IO (PutSignal a)++instance Monad Put where+  return x = Put $ \k -> k x+  {-# INLINE return #-}+  m >>= f  = Put $ \k -> unPut m (\x -> unPut (f x) k)+  {-# INLINE (>>=) #-}+  m >>  n  = Put $ \k -> unPut m (\_ -> unPut n k)+  {-# INLINE (>>) #-}++------------------------------------------------------------------------------+-- The Builder type with equal signals as the Put type+------------------------------------------------------------------------------++newtype Builder = Builder (forall r. PutStep r -> PutStep r)++instance Monoid Builder where+  mempty = Builder id+  {-# INLINE mempty #-}+  (Builder b1) `mappend` (Builder b2) = Builder $ b1 . b2+  {-# INLINE mappend #-}+  mconcat = foldr mappend mempty+  {-# INLINE mconcat #-}++putBuilder :: Builder -> Put ()+putBuilder (Builder build) = Put $ \k -> build (k ())++fromPut :: Put () -> Builder+fromPut (Put put) = Builder $ \k -> put (\_ -> k)++fromBuildStep :: (forall r. PutStep r -> BufRange -> IO (PutSignal r)) -> Builder+fromBuildStep step = Builder step'+  where+    step' k op ope = step k (BufRange (uptrToPtr op) (uptrToPtr ope))+{-# INLINE fromBuildStep #-}++callBuildStep :: PutStep a -> BufRange -> IO (PutSignal a)+callBuildStep k (BufRange op ope) = k (ptrToUPtr op) (ptrToUPtr ope)+{-# INLINE callBuildStep #-}++boxBuildStep :: PutStep a -> (BufRange -> IO (PutSignal a))+boxBuildStep step (BufRange op ope) = step (ptrToUPtr op) (ptrToUPtr ope)+{-# INLINE boxBuildStep #-}++unboxBuildStep :: (BufRange -> IO (PutSignal a)) -> PutStep a+unboxBuildStep step op ope = step (BufRange (uptrToPtr op) (uptrToPtr ope))+{-# INLINE unboxBuildStep #-}++fromWriteSingleton :: (a -> Write) -> a -> Builder+fromWriteSingleton write = +    mkBuilder+  where+    mkBuilder x = fromBuildStep step+      where+        step k (BufRange pf pe)+          | pf `plusPtr` size <= pe = do+              io pf+              let !br' = BufRange (pf `plusPtr` size) pe+              callBuildStep k br'+          | otherwise               = +              return $ BufferFull size pf (unboxBuildStep $ step k)+          where+            Write size io = write x+{-# INLINE fromWriteSingleton #-}++fromWord8 :: Word8 -> Builder+fromWord8 = fromWriteSingleton writeWord8+{-# INLINE fromWord8 #-}++------------------------------------------------------------------------------+-- More explicit implementation of running builders+------------------------------------------------------------------------------+++data Buffer = Buffer {-# UNPACK #-} !(ForeignPtr Word8) -- underlying pinned array+                     {-# UNPACK #-} !(Ptr Word8)        -- beginning of slice+                     {-# UNPACK #-} !(Ptr Word8)        -- next free byte+                     {-# UNPACK #-} !(Ptr Word8)        -- first byte after buffer++allocBuffer :: Int -> IO Buffer+allocBuffer size = do+    fpbuf <- S.mallocByteString size+    let !pbuf = unsafeForeignPtrToPtr fpbuf+    return $! Buffer fpbuf pbuf pbuf (pbuf `plusPtr` size)++unsafeFreezeBuffer :: Buffer -> S.ByteString+unsafeFreezeBuffer (Buffer fpbuf p0 op _) = +    S.PS fpbuf 0 (op `minusPtr` p0)++unsafeFreezeNonEmptyBuffer :: Buffer -> Maybe S.ByteString+unsafeFreezeNonEmptyBuffer (Buffer fpbuf p0 op _) +  | p0 == op  = Nothing+  | otherwise = Just $ S.PS fpbuf 0 (op `minusPtr` p0)++nextSlice :: Int -> Buffer -> Maybe Buffer+nextSlice minSize (Buffer fpbuf _ op ope)+  | ope `minusPtr` op <= minSize = Nothing+  | otherwise                    = Just (Buffer fpbuf op op ope)++runPut :: Monad m +       => (IO (PutSignal a) -> m (PutSignal a)) -- lifting of buildsteps+       -> (Int -> Buffer -> m Buffer) -- output function for a guaranteedly non-empty buffer, the returned buffer will be filled next+       -> (S.ByteString -> m ())    -- output function for guaranteedly non-empty bytestrings, that are inserted directly into the stream+       -> Put a                     -- put to execute+       -> Buffer                    -- initial buffer to be used+       -> m (a, Buffer)             -- result of put and remaining buffer+runPut liftIO outputBuf outputBS (Put put) =+    runStep (put $ (\x -> unboxBuildStep $ finalStep x))+  where+    finalStep x !(BufRange op _) = return $ Done op x++    runStep step buf@(Buffer fpbuf p0 op ope) = do+        let !br = BufRange op ope+        signal <- liftIO $ callBuildStep step br+        case signal of +            Done op' x ->         -- put completed, buffer partially runSteped+                return (x, Buffer fpbuf p0 op' ope)++            BufferFull minSize op' nextStep -> do+                buf' <- outputBuf minSize (Buffer fpbuf p0 op' ope)+                runStep nextStep buf'++            InsertByteString op' bs nextStep+              | S.null bs ->   -- flushing of buffer required+                  outputBuf 1 (Buffer fpbuf p0 op' ope) >>= runStep nextStep+              | p0 == op' -> do -- no bytes written: just insert bytestring+                  outputBS bs+                  runStep nextStep buf+              | otherwise -> do   -- bytes written, insert buffer and bytestring+                  buf' <- outputBuf 1 (Buffer fpbuf p0 op' ope)+                  outputBS bs+                  runStep nextStep buf'+{-# INLINE runPut #-}+              +-- | A monad for lazily composing lazy bytestrings using continuations.+newtype LBSM a = LBSM { unLBSM :: (a, L.ByteString -> L.ByteString) }++instance Monad LBSM where+    return x                       = LBSM (x, id)+    (LBSM (x,k)) >>= f             = let LBSM (x',k') = f x in LBSM (x', k . k')+    (LBSM (_,k)) >> (LBSM (x',k')) = LBSM (x', k . k')++-- | Execute a put and return the written buffers as the chunks of a lazy+-- bytestring.+toLazyByteString2 :: Put a -> L.ByteString+toLazyByteString2 put = +    k (bufToLBSCont (snd result) L.empty)+  where+    -- initial buffer+    buf0 = inlinePerformIO $ allocBuffer B.defaultBufferSize+    -- run put, but don't force result => we're lazy enough+    LBSM (result, k) = runPut liftIO outputBuf outputBS put buf0+    -- convert a buffer to a lazy bytestring continuation+    bufToLBSCont = maybe id L.Chunk . unsafeFreezeNonEmptyBuffer+    -- lifting an io putsignal to a lazy bytestring monad+    liftIO io = LBSM (inlinePerformIO io, id)+    -- add buffer as a chunk prepare allocation of new one+    outputBuf minSize buf = LBSM+        ( inlinePerformIO $ allocBuffer (max minSize B.defaultBufferSize)+        , bufToLBSCont buf )+    -- add bytestring directly as a chunk; exploits postcondition of runPut+    -- that bytestrings are non-empty+    outputBS bs = LBSM ((), L.Chunk bs)
blaze-builder.cabal view
@@ -1,6 +1,6 @@ Name:                blaze-builder-Version:             0.2.0.3-Synopsis:            Efficient construction of bytestrings.+Version:             0.2.1.0+Synopsis:            Efficient buffered output.  Description:                               This library provides an abstraction of buffered output of@@ -13,13 +13,18 @@                      when writing the resulting lazy bytestring to a file or                      sending it over the network. -Homepage:            http://github.com/jaspervdj/blaze-builder-Bug-Reports:         http://github.com/jaspervdj/blaze-builder/issues+Author:              Jasper Van der Jeugt, Simon Meier+Copyright:           2010 Simon Meier+                     2010 Jasper Van der Jeugt+Maintainer:          Simon Meier <iridcode@gmail.com>+ License:             BSD3 License-file:        LICENSE-Author:              Jasper Van der Jeugt, Simon Meier-Maintainer:          jaspervdj@gmail.com, iridcode@gmail.com++Homepage:            http://github.com/meiersi/blaze-builder+Bug-Reports:         http://github.com/meiersi/blaze-builder/issues Stability:           Experimental+ Category:            Data Build-type:          Simple Cabal-version:       >= 1.6@@ -37,20 +42,29 @@                       tests/*.hs +Source-repository head+  Type: git+  Location: https://github.com/meiersi/blaze-builder.git+ Library   ghc-options:       -Wall    exposed-modules:   Blaze.ByteString.Builder-                     Blaze.ByteString.Builder.Write                      Blaze.ByteString.Builder.Int                      Blaze.ByteString.Builder.Word                      Blaze.ByteString.Builder.ByteString                      Blaze.ByteString.Builder.Char.Utf8+                     Blaze.ByteString.Builder.Char8                      Blaze.ByteString.Builder.Html.Utf8+                     Blaze.ByteString.Builder.HTTP                       Blaze.ByteString.Builder.Internal+                     Blaze.ByteString.Builder.Internal.Write+                     Blaze.ByteString.Builder.Internal.Types+                     Blaze.ByteString.Builder.Internal.Buffer++  other-modules:     Blaze.ByteString.Builder.Internal.UncheckedShifts   -  -- FIXME: Ensure dependencies are strict enough   build-depends:     base == 4.* ,                      bytestring == 0.9.* ,                      text >= 0.10 && < 0.12