packages feed

blaze-builder 0.2.1.4 → 0.3.0.0

raw patch · 11 files changed

+697/−103 lines, 11 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Blaze.ByteString.Builder.Internal.Write: data WriteIO
- Blaze.ByteString.Builder.Internal.Write: instance Monoid 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: putLiftIO :: IO a -> Put a
+ Blaze.ByteString.Builder.Internal.Types: putLiftIO :: IO a -> Put a
+ Blaze.ByteString.Builder.Internal.Write: data Poke
+ Blaze.ByteString.Builder.Internal.Write: getBound :: Write -> Int
+ Blaze.ByteString.Builder.Internal.Write: getBound' :: String -> (a -> Write) -> Int
+ Blaze.ByteString.Builder.Internal.Write: getPoke :: Write -> Poke
+ Blaze.ByteString.Builder.Internal.Write: instance Monoid Poke
+ Blaze.ByteString.Builder.Internal.Write: pokeN :: Int -> (Ptr Word8 -> IO ()) -> Poke
+ Blaze.ByteString.Builder.Internal.Write: runPoke :: Poke -> Ptr Word8 -> IO (Ptr Word8)
+ Blaze.ByteString.Builder.Internal.Write: writeEq :: Eq a => a -> (a -> Write) -> (a -> Write) -> (a -> Write)
+ Blaze.ByteString.Builder.Internal.Write: writeIf :: (a -> Bool) -> (a -> Write) -> (a -> Write) -> (a -> Write)
+ Blaze.ByteString.Builder.Internal.Write: writeLiftIO :: (a -> Write) -> IO a -> Write
+ Blaze.ByteString.Builder.Internal.Write: writeOrd :: Ord a => a -> (a -> Write) -> (a -> Write) -> (a -> Write) -> (a -> Write)
+ Blaze.ByteString.Builder.Internal.Write: writeOrdering :: (a -> Ordering) -> (a -> Write) -> (a -> Write) -> (a -> Write) -> (a -> Write)
- Blaze.ByteString.Builder: fromWriteSingleton :: (a -> Write) -> a -> Builder
+ Blaze.ByteString.Builder: fromWriteSingleton :: (a -> Write) -> (a -> Builder)
- Blaze.ByteString.Builder.Internal.Write: boundedWrite :: Int -> WriteIO -> Write
+ Blaze.ByteString.Builder.Internal.Write: boundedWrite :: Int -> Poke -> Write
- Blaze.ByteString.Builder.Internal.Write: fromWriteSingleton :: (a -> Write) -> a -> Builder
+ Blaze.ByteString.Builder.Internal.Write: fromWriteSingleton :: (a -> Write) -> (a -> Builder)
- Blaze.ByteString.Builder.Internal.Write: runWrite :: Write -> WriteIO
+ Blaze.ByteString.Builder.Internal.Write: runWrite :: Write -> Ptr Word8 -> IO (Ptr Word8)

Files

Blaze/ByteString/Builder/Char/Utf8.hs view
@@ -42,19 +42,19 @@ writeChar :: Char -> Write writeChar c = boundedWrite 4 (encodeCharUtf8 f1 f2 f3 f4 c)   where-    f1 x1          = writeN 1 $ \op -> do pokeByteOff op 0 x1+    f1 x1          = pokeN 1 $ \op -> do pokeByteOff op 0 x1 -    f2 x1 x2       = writeN 2 $ \op -> do pokeByteOff op 0 x1-                                          pokeByteOff op 1 x2+    f2 x1 x2       = pokeN 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+    f3 x1 x2 x3    = pokeN 3 $ \op -> do pokeByteOff op 0 x1+                                         pokeByteOff op 1 x2+                                         pokeByteOff op 2 x3 -    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+    f4 x1 x2 x3 x4 = pokeN 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
Blaze/ByteString/Builder/HTTP.hs view
@@ -36,7 +36,7 @@ {-# INLINE execWrite #-} execWrite :: Write -> Ptr Word8 -> IO () execWrite w op = do-    _ <- runWriteIO (runWrite w) op+    _ <- runPoke (getPoke w) op     return ()  @@ -95,7 +95,7 @@  writeWord32Hex :: Word32 -> Write writeWord32Hex w = -    boundedWrite (2 * sizeOf w) (writeN len $ pokeWord32HexN len w)+    boundedWrite (2 * sizeOf w) (pokeN len $ pokeWord32HexN len w)   where     len = word32HexLength w {-# INLINE writeWord32Hex #-}@@ -181,7 +181,7 @@                       wrapChunk opInner' $ \op' -> do                         -- add header for inserted bytestring                         -- FIXME: assert(S.length bs < maxBound :: Word32)-                        !op'' <- (`runWriteIO` op') $ runWrite $+                        !op'' <- (`runPoke` op') $ getPoke $                             writeWord32Hex (fromIntegral $ S.length bs)                              `mappend` writeCRLF                         -- insert bytestring and write CRLF in next buildstep
Blaze/ByteString/Builder/Html/Utf8.hs view
@@ -46,12 +46,12 @@     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+    io '<'  = getPoke $ writeByteString "&lt;"+    io '>'  = getPoke $ writeByteString "&gt;"+    io '&'  = getPoke $ writeByteString "&amp;"+    io '"'  = getPoke $ writeByteString "&quot;"+    io '\'' = getPoke $ writeByteString "&#39;"+    io c    = getPoke $ writeChar c {-# INLINE writeHtmlEscapedChar #-}  -- | /O(1)./ Serialize a HTML escaped Unicode character using the UTF-8
Blaze/ByteString/Builder/Internal.hs view
@@ -30,6 +30,7 @@   , Put   , putBuilder   , putBuildStepCont+  , putLiftIO    -- * Writes   , module Blaze.ByteString.Builder.Internal.Write
Blaze/ByteString/Builder/Internal/Types.hs view
@@ -134,4 +134,10 @@ fromPut :: Put a -> Builder fromPut (Put put) = Builder $ \k -> put (\_ -> k) +-- Lifting IO actions+--------------------- +-- | Lift the given IO action.+{-# INLINE putLiftIO #-}+putLiftIO :: IO a -> Put a+putLiftIO io = putBuildStepCont $ \k br -> io >>= (`k` br)
Blaze/ByteString/Builder/Internal/Write.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE CPP, BangPatterns, MonoPatBinds #-} -- |--- Module      : Blaze.ByteString.Builder.Internal.WriteIO+-- Module      : Blaze.ByteString.Builder.Internal.Poke -- Copyright   : (c) 2010 Simon Meier --               (c) 2010 Jasper van der Jeugt -- License     : BSD3-style (see LICENSE)@@ -12,15 +12,28 @@ -- A general and efficient write type that allows for the easy construction of -- builders for (smallish) bounded size writes to a buffer. --+-- FIXME: Improve documentation.+-- module Blaze.ByteString.Builder.Internal.Write (-  -- * Abstracting writes to a buffer-    Write-  , WriteIO-  , writeN+  -- * Poking a buffer+    Poke+  , runPoke+  , pokeN++  -- * Writing to abuffer+  , Write+  , runWrite+  , getBound+  , getBound'+  , getPoke+   , exactWrite   , boundedWrite-  , runWrite-  , runWriteIO+  , writeLiftIO+  , writeIf+  , writeEq+  , writeOrdering+  , writeOrd    -- * Constructing builders from writes   , fromWrite@@ -44,7 +57,7 @@   --------------------------------------------------------------------------------- The Write WriteIO Type+-- Poking a buffer and writing to a buffer ------------------------------------------------------------------------------  -- Sadly GHC is not smart enough: code where we branch and each branch should@@ -52,80 +65,167 @@ -- 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+-- than the one for Poke. 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) }+-- | Changing a sequence of bytes starting from the given pointer. 'Poke's are+-- the most primitive buffer manipulation. In most cases, you don't use the+-- explicitely but as part of a 'Write', which also tells how many bytes will+-- be changed at most. +newtype Poke = +    Poke { runPoke :: Ptr Word8 -> IO (Ptr Word8) }  -- | A write of a bounded number of bytes.-data Write = Write {-# UNPACK #-} !Int WriteIO+--+-- When defining a function @write :: a -> Write@ for some @a@, then it is+-- important to ensure that the bound on the number of bytes written is+-- data-independent. Formally, +--+--  @ forall x y. getBound (write x) = getBound (write y) @+--+-- The idea is that this data-independent bound is specified such that the+-- compiler can optimize the check, if there are enough free bytes in the buffer,+-- to a single subtraction between the pointer to the next free byte and the+-- pointer to the end of the buffer with this constant bound of the maximal+-- number of bytes to be written.+--+data Write = Write {-# UNPACK #-} !Int Poke --- | Extract the 'WriteIO' action of a write.+-- | Extract the 'Poke' action of a write.+{-# INLINE getPoke #-}+getPoke :: Write -> Poke+getPoke (Write _ wio) = wio++-- | Run the 'Poke' action of a write. {-# INLINE runWrite #-}-runWrite :: Write -> WriteIO-runWrite (Write _ wio) = wio+runWrite :: Write -> Ptr Word8 -> IO (Ptr Word8)+runWrite = runPoke . getPoke -instance Monoid WriteIO where-  mempty = WriteIO $ return+-- | Extract the maximal number of bytes that this write could write.+{-# INLINE getBound #-}+getBound :: Write -> Int+getBound (Write bound _) = bound++-- | Extract the maximal number of bytes that this write could write in any+-- case. Assumes that the bound of the write is data-independent.+{-# INLINE getBound' #-}+getBound' :: String             -- ^ Name of caller: for debugging purposes.+          -> (a -> Write) +          -> Int+getBound' msg write =+    getBound $ write $ error $ +    "getBound' called from " ++ msg ++ ": write bound is not data-independent."++instance Monoid Poke where   {-# INLINE mempty #-}-  (WriteIO w1) `mappend` (WriteIO w2) = WriteIO $ w1 >=> w2+  mempty = Poke $ return+   {-# INLINE mappend #-}-  mconcat = foldr mappend mempty+  (Poke po1) `mappend` (Poke po2) = Poke $ po1 >=> po2+   {-# INLINE mconcat #-}+  mconcat = foldr mappend mempty  instance Monoid Write where-  mempty = Write 0 mempty   {-# INLINE mempty #-}+  mempty = Write 0 mempty++  {-# INLINE mappend #-}   (Write bound1 w1) `mappend` (Write bound2 w2) =     Write (bound1 + bound2) (w1 `mappend` w2)-  {-# INLINE mappend #-}-  mconcat = foldr mappend mempty+     {-# INLINE mconcat #-}+  mconcat = foldr mappend mempty  --- | @writeN size io@ creates a write that denotes the writing of @size@ bytes+-- | @pokeN 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 #-}+{-# INLINE pokeN #-}+pokeN :: Int +       -> (Ptr Word8 -> IO ()) -> Poke+pokeN size io = Poke $ \op -> io op >> return (op `plusPtr` size)   -- | @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!+{-# INLINE exactWrite #-} exactWrite :: Int             -> (Ptr Word8 -> IO ())             -> Write-exactWrite size io = Write size (writeN size io)-{-# INLINE exactWrite #-}+exactWrite size io = Write size (pokeN size io)  -- | @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 #-}+boundedWrite :: Int -> Poke -> Write+boundedWrite = Write +-- | @writeLiftIO io write@ creates a write executes the @io@ action to compute+-- the value that is then written.+{-# INLINE writeLiftIO #-}+writeLiftIO :: (a -> Write) -> IO a -> Write+writeLiftIO write io =+    Write (getBound' "writeLiftIO" write) +          (Poke $ \pf -> do x <- io; runWrite (write x) pf)++-- | @writeIf p wTrue wFalse x@ creates a 'Write' with a 'Poke' equal to @wTrue+-- x@, if @p x@ and equal to @wFalse x@ otherwise. The bound of this new+-- 'Write' is the maximum of the bounds for either 'Write'. This yields a data+-- independent bound, if the bound for @wTrue@ and @wFalse@ is already data+-- independent.+{-# INLINE writeIf #-}+writeIf :: (a -> Bool) -> (a -> Write) -> (a -> Write) -> (a -> Write)+writeIf p wTrue wFalse x = +    boundedWrite (max (getBound $ wTrue x) (getBound $ wFalse x)) +                 (if p x then getPoke $ wTrue x else getPoke $ wFalse x)++-- | Compare the value to a test value and use the first write action for the+-- equal case and the second write action for the non-equal case.+{-# INLINE writeEq #-}+writeEq :: Eq a => a -> (a -> Write) -> (a -> Write) -> (a -> Write)+writeEq test = writeIf (test ==)++-- | TODO: Test this. It might well be too difficult to use.+--   FIXME: Better name required!+{-# INLINE writeOrdering #-}+writeOrdering :: (a -> Ordering) +              -> (a -> Write) -> (a -> Write) -> (a -> Write)+              -> (a -> Write)+writeOrdering ord wLT wEQ wGT x = +    boundedWrite bound (case ord x of LT -> getPoke $ wLT x; +                                      EQ -> getPoke $ wEQ x; +                                      GT -> getPoke $ wGT x)+  where+    bound = max (getBound $ wLT x) (max (getBound $ wEQ x) (getBound $ wGT x))++-- | A write combinator useful to build decision trees for deciding what value+-- to write with a constant bound on the maximal number of bytes written.+{-# INLINE writeOrd #-}+writeOrd :: Ord a +       => a+       -> (a -> Write) -> (a -> Write) -> (a -> Write)+       -> (a -> Write)+writeOrd test = writeOrdering (`compare` test)++-- | Create a builder that execute a single 'Write'.+{-# INLINE fromWrite #-} fromWrite :: Write -> Builder fromWrite (Write maxSize wio) =     fromBuildStepCont step   where     step k (BufRange op ope)       | op `plusPtr` maxSize <= ope = do-          op' <- runWriteIO wio op+          op' <- runPoke wio op           let !br' = BufRange op' ope           k br'       | otherwise = return $ bufferFull maxSize op (step k)-{-# INLINE fromWrite #-} -fromWriteSingleton :: (a -> Write) -> a -> Builder+{-# INLINE fromWriteSingleton #-}+fromWriteSingleton :: (a -> Write) -> (a -> Builder) fromWriteSingleton write =      mkBuilder   where@@ -133,13 +233,12 @@       where         step k (BufRange op ope)           | op `plusPtr` maxSize <= ope = do-              op' <- runWriteIO wio op+              op' <- runPoke 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@@ -156,7 +255,7 @@              go xs@(x':xs') !op               | op `plusPtr` maxSize <= ope0 = do-                  !op' <- runWriteIO wio op+                  !op' <- runPoke wio op                   go xs' op'               | otherwise = return $ bufferFull maxSize op (step xs k)               where
LICENSE view
@@ -1,4 +1,4 @@-Copyright Jasper Van der Jeugt 2010+Copyright Jasper Van der Jeugt 2010, Simon Meier 2010 & 2011  All rights reserved. 
Makefile view
@@ -7,7 +7,7 @@ #########  GHC6 = ghc-6.12.3-GHC7 = ghc-7.0.1+GHC7 = ghc-7.0.2  GHC = $(GHC7) @@ -22,11 +22,12 @@ clean-bench-all: 	rm -f benchmarks/*.o benchmarks/*.hi 	rm -f benchmarks/Throughput/*.o benchmarks/Throughput/*.hi-	rm -f Text/Blaze/Builder.o Text/Blaze/Builder.hi-	rm -f Text/Blaze/Builder/*.o Text/Blaze/Builder/*.hi-	rm -f Text/Blaze/Builder/Char/*.o Text/Blaze/Builder/Char/*.hi-	rm -f Text/Blaze/Builder/Html/*.o Text/Blaze/Builder/Html/*.hi-	rm -f Text/Blaze/Builder/Core/*.o Text/Blaze/Builder/Core/*.hi+	rm -f Blaze/ByteString/Builder.o Blaze/ByteString/Builder.hi+	rm -f Blaze/ByteString/Builder/*.o Blaze/ByteString/Builder/*.hi+	rm -f Blaze/ByteString/Builder/Internal/*.o Blaze/ByteString/Builder/Internal/*.hi+	rm -f Blaze/ByteString/Builder/Char/*.o Blaze/ByteString/Builder/Char/*.hi+	rm -f Blaze/ByteString/Builder/Html/*.o Blaze/ByteString/Builder/Html/*.hi+	rm -f Blaze/ByteString/Builder/Core/*.o Blaze/ByteString/Builder/Core/*.hi 	rm -f benchmarks/Compression benchmarks/StringAndText benchmarks/BenchThroughput benchmarks/ChunkedWrite benchmarks/BlazeVsBinary 	rm -f Criterion/*.o Criterion/*.hi 	rm -f Criterion/ScalingBenchmark@@ -34,6 +35,16 @@ ## Individual benchmarks ######################## +# utf8 writing to a file+utf8-io:+	$(GHC) --make -O2 -fforce-recomp -main-is Utf8IO benchmarks/Utf8IO.hs+	time ./benchmarks/Utf8IO via-text    100000000 /dev/null+	time ./benchmarks/Utf8IO text        100000000 /dev/null+	time ./benchmarks/Utf8IO blaze       100000000 /dev/null+	time ./benchmarks/Utf8IO base        100000000 /dev/null+	time ./benchmarks/Utf8IO utf8-light  100000000 /dev/null+	time ./benchmarks/Utf8IO utf8-string 100000000 /dev/null+ # 'blaze-builder' vs. 'binary' comparision bench-blaze-vs-binary: 	$(GHC) --make -O2 -fforce-recomp -main-is BlazeVsBinary benchmarks/BlazeVsBinary.hs@@ -101,6 +112,9 @@ bench-lazy-bytestring: 	$(GHC) --make -O2 -fforce-recomp -ibenchmarks -main-is LazyByteString LazyByteString 	./benchmarks/LazyByteString --resamples 10000++core-lazy-bytestring:+	ghc-core -- --make -O2 -fforce-recomp -ibenchmarks -main-is LazyByteString LazyByteString  # Benchmark benefit of compaction before compression bench-server:
benchmarks/LazyByteString.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE BangPatterns, OverloadedStrings #-} -- | -- Module      : LazyByteString -- Copyright   : (c) 2010 Simon Meier@@ -13,20 +13,25 @@ -- implemented with slicing only. module LazyByteString where -- (main)  where +import Data.Char import Data.Word import Data.Monoid  import Data.List  +import Control.Monad+import Control.Arrow (second) import Criterion.Main  import Foreign  import qualified Data.ByteString               as S+import qualified Data.ByteString.Unsafe        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 Data.ByteString.Base64+ import Blaze.ByteString.Builder.Internal-import Blaze.ByteString.Builder.Write import Blaze.ByteString.Builder.Word import Blaze.ByteString.Builder.ByteString @@ -37,7 +42,38 @@ main :: IO () main = do     let (chunkInfos, benchmarks) = unzip +          {-           [ lazyVsBlaze+              ( "partitionLazy"+              , (uncurry mappend) . L.partition ((0 <) . sin . fromIntegral)+              , (uncurry mappend) . partitionLazy ((0 <) . sin . fromIntegral)+              , (\i -> L.drop 13 $ L.pack $ take i $ cycle [0..])+              , n)+          -}+          {-+          [ lazyVsBlaze+              ( "base64mime"+              , L.fromChunks . return . joinWith "\r\n" 76 . encode+              , toLazyByteString . encodeBase64MIME+              , (\i -> S.drop 13 $ S.pack $ take i $ cycle [0..])+              , n)+          -}+          {-+          [ lazyVsBlaze+              ( "joinWith"+              , L.fromChunks . return . joinWith "\r\n" 76+              , toLazyByteString . intersperseBlocks 76 "\r\n"+              , (\i -> S.drop 13 $ S.pack $ take i $ cycle [0..])+              , n)+          -}+          [ lazyVsBlaze+              ( "base64"+              , L.fromChunks . return . encode+              , toLazyByteString . encodeBase64+              , (\i -> S.drop 13 $ S.pack $ take i $ cycle [0..])+              , n)+          {-+          , lazyVsBlaze               ( "copy"               , L.copy               , copyBlaze@@ -67,6 +103,7 @@               , unfoldrBlaze countToZero               , id               , n )+          -}           ]     sequence_ (intersperse (putStrLn "") chunkInfos)     putStrLn ""@@ -82,8 +119,8 @@          showChunksize implLazy  lazy          showChunksize implBlaze blaze     , bgroup cmpName -        [ mkBench implLazy  lazy-        , mkBench implBlaze blaze+        [ mkBench implBlaze blaze+        , mkBench implLazy  lazy         ]     )   where@@ -145,27 +182,31 @@ fromWriteUnfoldr write =      makeBuilder   where-    makeBuilder f x0 = Builder $ step x0+    makeBuilder f x0 = fromBuildStepCont $ step x0       where         step x1 !k = fill x1           where-            fill x !pf0 !pe0 = go (f x) pf0+            fill x !(BufRange pf0 pe0) = go (f x) pf0               where-                go !Nothing        !pf = k pf pe0+                go !Nothing        !pf = do+                    let !br' = BufRange pf pe0+                    k br'                 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+                  | pf `plusPtr` bound <= pe0 = do+                      !pf' <- runWrite (write y) pf+                      go (f x') pf'+                  | otherwise = return $ bufferFull bound pf $ +                      \(BufRange pfNew peNew) -> do +                          !pfNew' <- runWrite (write y) pfNew+                          fill x' (BufRange pfNew' peNew)                   where-                    !(Write size io) = write y+                    bound = getBound $ write y {-# INLINE fromWriteUnfoldr #-}  -- Filtering and mapping ------------------------ +test :: Int -> (L.ByteString, L.ByteString) test i =      ((L.filter ((==0) . (`mod` 3)) $ x) ,      (filterBlaze ((==0) . (`mod` 3)) $ x))@@ -199,17 +240,17 @@ mapFilterMapByteString :: (Word8 -> Word8) -> (Word8 -> Bool) -> (Word8 -> Word8)                         -> S.ByteString -> Builder mapFilterMapByteString f p g = -    \bs -> Builder $ step bs+    \bs -> fromBuildStepCont $ 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+        goBS !ip0 !br@(BufRange op0 ope)           | ip0 >= ipe = do touchForeignPtr ifp -- input buffer consumed-                            k op0 ope+                            k br           | op0 < ope  = goPartial (ip0 `plusPtr` min outRemaining inpRemaining)-          | otherwise  = return $ BufferFull 1 op0 (goBS ip0) +          | otherwise  = return $ bufferFull 1 op0 (goBS ip0)            where             outRemaining = ope `minusPtr` op0             inpRemaining = ipe `minusPtr` ip0@@ -223,7 +264,7 @@                         then poke op (f w') >> go (ip `plusPtr` 1) (op `plusPtr` 1)                         else                   go (ip `plusPtr` 1) op                   | otherwise =-                      goBS ip op ope+                      goBS ip (BufRange op ope) {-# INLINE mapFilterMapByteString #-}  mapFilterMapLazyByteString :: (Word8 -> Word8) -> (Word8 -> Bool) -> (Word8 -> Word8) @@ -257,39 +298,43 @@ fromWriteReplicated write =      makeBuilder   where-    makeBuilder !n0 x = Builder $ step +    makeBuilder !n0 x = fromBuildStepCont $ step        where-        Write size io = write x+        bound = getBound $ write x         step !k = fill n0           where-            fill !n1 !pf0 !pe0 = go n1 pf0+            fill !n1 !(BufRange pf0 pe0) = go n1 pf0               where-                go 0 !pf = k pf pe0+                go 0 !pf = do+                    let !br' = BufRange pf pe0+                    k br'                 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+                  | pf `plusPtr` bound <= pe0 = do+                      pf' <- runWrite (write x) pf+                      go (n-1) pf'+                  | otherwise = return $ bufferFull bound pf $ +                      \(BufRange pfNew peNew) -> do +                          pfNew' <- runWrite (write x) pfNew+                          fill (n-1) (BufRange pfNew' peNew) {-# INLINE fromWriteReplicated #-}  -- FIXME: Output repeated bytestrings for large replications. fromReplicateWord8 :: Int -> Word8 -> Builder fromReplicateWord8 !n0 x = -    Builder $ step+    fromBuildStepCont $ step   where     step !k = fill n0       where-        fill !n !pf !pe-          | n <= 0    = k pf pe+        fill !n !br@(BufRange pf pe)+          | n <= 0    = k br           | pf' <= pe = do               _ <- S.memset pf x (fromIntegral n) -- FIXME: This conversion looses information for 64 bit systems.-              k pf' pe+              let !br' = BufRange pf' pe+              k br'           | 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)+              return $ bufferFull 1 pe $ fill (n - l)           where             pf' = pf `plusPtr` n {-# INLINE fromReplicateWord8 #-}@@ -370,7 +415,7 @@ ----------  packBlaze :: [Word8] -> L.ByteString-packBlaze = toLazyByteString . fromWrite1List writeWord8+packBlaze = toLazyByteString . fromWriteList writeWord8   -- Reverse@@ -399,7 +444,6 @@ -- copy ------- --- FIXME: Implement wrapping copyBlaze :: L.ByteString -> L.ByteString copyBlaze = toLazyByteString . copyLazyByteString @@ -407,4 +451,332 @@ -- ?? packCString, packCStringLen --------------------------------- +-- joinWith+-------------------------------------------- +intersperseBlocks :: Int -> S.ByteString -> S.ByteString -> Builder+intersperseBlocks blockSize sep (S.PS ifp ioff isize) = +    fromPut $ do+        lastBS <- go (ip0 `plusPtr` ioff) +        unless (S.null lastBS) (putBuilder $ fromByteString lastBS)+  where+    ip0 = unsafeForeignPtrToPtr ifp+    ipe = ip0 `plusPtr` (ioff + isize)+    go !ip +      | ip `plusPtr` blockSize >= ipe =+          return $ S.PS ifp (ip `minusPtr` ip0) (ipe `minusPtr` ip)+      | otherwise = do+          putBuilder $ fromByteString (S.PS ifp (ip `minusPtr` ip0) blockSize)+                       `mappend` fromByteString sep+          go (ip `plusPtr` blockSize)++intersperseLazyBlocks :: Int -> Builder -> L.ByteString -> Builder+intersperseLazyBlocks blockSize sep bs =+    go (splitLazyAt blockSize bs)+  where+    go (pre, suf)+      | L.null suf = fromLazyByteString pre+      | otherwise  = fromLazyByteString pre `mappend` sep `mappend`+                     go (splitLazyAt blockSize suf)++encodeBase64MIME :: S.ByteString -> Builder+encodeBase64MIME =+  intersperseLazyBlocks 76 (fromByteString "\r\n") . toLazyByteString . encodeBase64+++-- test blockwise mapping on base64 encoding+--------------------------------------------++-- | Encode a bytestring using Base64 encoding according to the specification+-- in RFC 4648, <http://www.apps.ietf.org/rfc/rfc4648.html>.+--+-- Note that you need to insert additional linebreaks every 76 bytes using the+-- function @joinWith "\r\n" 76@ in order to achieve the MIME Base64+-- Content-Transfer-Encoding <specified in http://tools.ietf.org/html/rfc2045>.+--+-- TODO implement encoding of lazy bytestrings, implement joinWith+-- functionality, and convencience function for MIME base-64 encoding.+encodeBase64 :: S.ByteString -> Builder+encodeBase64 = encodeLazyBase64 . L.fromChunks . return++encodeLazyBase64 :: L.ByteString -> Builder+encodeLazyBase64 = +    mkBuilder+  where+    mkBuilder bs = fromPut $ do+        remainder <- putWriteLazyBlocks 3 writeBase64 bs +        putBuilder $ complete remainder++    {-# INLINE writeBase64 #-}+    writeBase64 ip = +        exactWrite 4 $ \op -> do+            b0 <- peekByte 0+            b1 <- peekByte 1+            b2 <- peekByte 2+            let w = (b0 `shiftL` 16) .|. (b1 `shiftL` 8) .|. b2+            poke (castPtr $ op            ) =<< enc (w `shiftR` 12)+            poke (castPtr $ op `plusPtr` 2) =<< enc (w .&.   0xfff)+      where+        peekByte :: Int -> IO Word32+        peekByte off = fmap fromIntegral (peekByteOff ip off :: IO Word8)+        +        enc = peekElemOff (unsafeForeignPtrToPtr encodeTable) . fromIntegral++    {-# INLINE complete #-}+    complete bs+      | S.null bs = mempty+      | otherwise = fromWrite $+          exactWrite 4 $ \op -> do+              let poke6Base64 off sh = pokeByteOff op off+                      (alphabet `S.unsafeIndex` fromIntegral (w `shiftR` sh .&. 63))+                  pad off = pokeByteOff op off (fromIntegral $ ord '=' :: Word8)+              poke6Base64 0 18+              poke6Base64 1 12+              if S.length bs == 1 then pad 2 +                                  else poke6Base64 2 8+              pad 3+      where+        getByte :: Int -> Int -> Word32+        getByte i sh = fromIntegral (bs `S.unsafeIndex` i) `shiftL` sh+        w = getByte 0 16 .|. (if S.length bs == 1 then 0 else getByte 1 8)+            +    -- Lookup table trick from Data.ByteString.Base64 by Bryan O'Sullivan+    {-# NOINLINE alphabet #-}+    alphabet :: S.ByteString+    alphabet = S.pack $ [65..90] ++ [97..122] ++ [48..57] ++ [43,47]++    -- FIXME: Check that the implementation of the lookup table aslo works on+    -- big-endian systems.+    {-# NOINLINE encodeTable #-} +    encodeTable :: ForeignPtr Word16+    encodeTable = unsafePerformIO $ do+        fp <- mallocForeignPtrArray 4096+        let ix = fromIntegral . S.index alphabet+        withForeignPtr fp $ \p ->+          sequence_ [ pokeElemOff p (j*64+k) ((ix k `shiftL` 8) .|. ix j)+                    | j <- [0..63], k <- [0..63] ]+        return fp+++-- | Process a bytestring block-wise using a 'Write' action to produce the+-- output per block.+--+-- TODO: Compare speed with 'mapFilterMapByteString'.+{-# INLINE putWriteBlocks #-}+putWriteBlocks :: Int                  -- ^ Block size.+               -> (Ptr Word8 -> Write) -- ^ 'Write' given a pointer to the+                                       --   beginning of the block.+               -> S.ByteString         -- ^ 'S.ByteString' to consume blockwise.+               -> Put S.ByteString     -- ^ 'Put' returning the remaining+                                       --   bytes, which are guaranteed to be+                                       --   fewer than the block size.+putWriteBlocks blockSize write =+    \bs -> putBuildStepCont $ step bs+  where+    step (S.PS ifp ioff isize) !k = +        goBS (unsafeForeignPtrToPtr ifp `plusPtr` ioff)+      where+        !ipe = unsafeForeignPtrToPtr ifp `plusPtr` (ioff + isize)+        goBS !ip0 !br@(BufRange op0 ope)+          | ip0 `plusPtr` blockSize > ipe = do +              touchForeignPtr ifp -- input buffer consumed+              let !bs' = S.PS ifp (ip0 `minusPtr` unsafeForeignPtrToPtr ifp) +                                  (ipe `minusPtr` ip0)+              k bs' br++          | op0 `plusPtr` writeBound < ope  = +              goPartial (ip0 `plusPtr` (blockSize * min outRemaining inpRemaining))++          | otherwise  = return $ bufferFull writeBound op0 (goBS ip0) +          where+            writeBound   = getBound' "putWriteBlocks" write +            outRemaining = (ope `minusPtr` op0) `div` writeBound+            inpRemaining = (ipe `minusPtr` ip0) `div` blockSize++            goPartial !ipeTmp = go ip0 op0+              where+                go !ip !op+                  | ip < ipeTmp = do+                      op' <- runWrite (write ip) op+                      go (ip `plusPtr` blockSize) op'+                  | otherwise =+                      goBS ip (BufRange op ope)+++{-# INLINE putWriteLazyBlocks #-}+putWriteLazyBlocks :: Int                  -- ^ Block size.+                   -> (Ptr Word8 -> Write) -- ^ 'Write' given a pointer to the+                                           --   beginning of the block.+                   -> L.ByteString         -- ^ 'L.ByteString' to consume blockwise.+                   -> Put S.ByteString     -- ^ 'Put' returning the remaining+                                           --   bytes, which are guaranteed to be+                                           --   fewer than the block size.+putWriteLazyBlocks blockSize write =+    go+  where+    go L.Empty          = return S.empty+    go (L.Chunk bs lbs) = do+      bsRem <- putWriteBlocks blockSize write bs+      case S.length bsRem of+        lRem +          | lRem <= 0 -> go lbs+          | otherwise -> do+              let (lbsPre, lbsSuf) = +                      L.splitAt (fromIntegral $ blockSize - lRem) lbs+              case S.concat $ bsRem : L.toChunks lbsPre of+                block@(S.PS bfp boff bsize)+                  | bsize < blockSize -> return block+                  | otherwise         -> do+                      putBuilder $ fromWrite $ +                        write (unsafeForeignPtrToPtr bfp `plusPtr` boff)+                      putLiftIO $ touchForeignPtr bfp+                      go lbsSuf +                          ++------------------------------------------------------------------------------+-- Testing code+------------------------------------------------------------------------------+++chunks3 :: [Word8] -> [Word32]+chunks3 (b0 : b1 : b2 : bs) = +    ((fromIntegral b0 `shiftL` 16) .|. +     (fromIntegral b1 `shiftL`  8) .|. +     (fromIntegral b2            )+    ) : chunks3 bs+chunks3 _                   = []++cmpWriteToLib :: [Word8] -> (L.ByteString, L.ByteString)+cmpWriteToLib bs = +    -- ( toLazyByteString $ fromWriteList write24bitsBase64 $ chunks3 bs+    ( toLazyByteString $ encodeBase64 $ S.pack bs+    , (`L.Chunk` L.empty) $ encode $ S.pack bs )++test3 :: Bool+test3 = uncurry (==) $ cmpWriteToLib $ [0..]++test2 :: L.ByteString +test2 = toLazyByteString $ encodeBase64 $ S.pack [0..]++{- OLD code+ +{-# INLINE poke8 #-}+poke8 :: Word8 -> Ptr Word8 -> IO ()+poke8 = flip poke++-- | @writeBase64 w@ writes the lower @24@ bits as four times 6 bit in+-- little-endian order encoded using the standard alphabeth of Base 64 encoding+-- as defined in <http://www.apps.ietf.org/rfc/rfc4648.html>.+--+{-# INLINE write6bitsBase64 #-}+write6bitsBase64 :: Word32 -> Write+write6bitsBase64 = exactWrite 1  . poke6bitsBase64++{-# INLINE poke6bitsBase64 #-}+poke6bitsBase64 :: Word32 -> Ptr Word8 -> IO ()+poke6bitsBase64 w = poke8 (alphabet `S.unsafeIndex` fromIntegral (w .&. 63))+    {-+    | i <  26   = withOffsets  0 'A'+    | i <  52   = withOffsets 26 'a'+    | i <  62   = withOffsets 52 '0'+    | i == 62   = poke8 $ fromIntegral $ ord '+'+    | otherwise = poke8 $ fromIntegral $ ord '/'+  where+    i :: Int+    i = fromIntegral (w .&. 63)++    {-# INLINE withOffsets #-}+    withOffsets neg pos = poke8 $ fromIntegral (i + ord pos - neg)+    -}++{-# INLINE writePaddedBitsBase64 #-}+writePaddedBitsBase64 :: Bool             -- ^ Only 8 bits have to be output.+                      -> Word32           -- ^ Input whose lower 8 or 16 bits need to be output.+                      -> Write+writePaddedBitsBase64 only8 w =+    write6bitsBase64 (w `shiftr_w32` 18)                         `mappend`+    write6bitsBase64 (w `shiftr_w32` 12)                         `mappend`+    writeIf (const only8) (const $ C8.writeChar '=')+                          (write6bitsBase64 . (`shiftr_w32`  6)) +                          w                                      `mappend`+    C8.writeChar '='++{-# INLINE write24bitsBase64 #-}+write24bitsBase64 :: Word32 -> Write+write24bitsBase64 w = write6bitsBase64 (w `shiftr_w32` 18) `mappend`+                      write6bitsBase64 (w `shiftr_w32` 12) `mappend`+                      write6bitsBase64 (w `shiftr_w32`  6) `mappend`+                      write6bitsBase64 (w                )++-- ASSUMES bits 25 - 31 are zero.+{-# INLINE write24bitsBase64' #-}+write24bitsBase64' :: Word32 -> Write+write24bitsBase64' w = +    exactWrite 4 $ \p -> do+      poke (castPtr p              ) =<< enc (w `shiftR` 12)+      poke (castPtr $ p `plusPtr` 2) =<< enc (w .&.   0xfff)+  where+    {-# INLINE enc #-}+    enc = peekElemOff (unsafeForeignPtrToPtr encodeTable) . fromIntegral++-}++-------------------------------------------------------------------------------+-- A faster split for lazy bytestrings+-------------------------------------------------------------------------------++-- | /O(n\/c)/ 'splitAt' @n xs@ is equivalent to @('take' n xs, 'drop' n xs)@.+splitLazyAt :: Int -> L.ByteString -> (L.ByteString, L.ByteString)+splitLazyAt n cs0+  | n <= 0    = (L.Empty, cs0)+  | otherwise = split cs0+  where+    split L.Empty        = (L.Empty, L.Empty)+    split (L.Chunk c cs)+      | n < len  = case S.splitAt    n         c  of+          (pre, suf) -> (L.Chunk pre L.Empty, L.Chunk suf cs)+      | otherwise = case splitLazyAt (n - len) cs of+          (pre, suf) -> (L.Chunk c   pre    , suf           )+      where+        len = S.length c+++-------------------------------------------------------------------------------+-- A faster partition for strict and lazy bytestrings+-------------------------------------------------------------------------------++{-# INLINE partitionStrict #-}+partitionStrict :: (Word8 -> Bool) -> S.ByteString -> (S.ByteString, S.ByteString)+partitionStrict f (S.PS ifp ioff ilen) = +    second S.reverse $ S.inlinePerformIO $ do+        ofp <- S.mallocByteString ilen+        withForeignPtr ifp $ wrapper ofp+  where+    wrapper !ofp !ip0 = +        go (ip0 `plusPtr` ioff) op0 (op0 `plusPtr` ilen)+      where+        op0 = unsafeForeignPtrToPtr ofp++        go !ip !opl !oph+          | oph == opl = return (S.PS ofp 0 olen, S.PS ofp olen (ilen - olen))+          | otherwise  = do+              x <- peek ip+              if f x +                then do poke opl x +                        go (ip `plusPtr` 1) (opl `plusPtr` 1) oph+                else do let oph' = oph `plusPtr` (-1)+                        poke oph' x+                        go (ip `plusPtr` 1) opl               oph'++          where+            olen = opl `minusPtr` op0++{-# INLINE partitionLazy #-}+partitionLazy :: (Word8 -> Bool) -> L.ByteString -> (L.ByteString, L.ByteString)+partitionLazy f = +    L.foldrChunks partitionOne (L.empty, L.empty)+  where+    partitionOne bs (ls, rs) = +        (L.Chunk l ls, L.Chunk r rs)+      where+        (l, r) = partitionStrict f bs
+ benchmarks/Utf8IO.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Copyright   : (c) 2011 Simon Meier+-- License     : BSD3-style (see LICENSE)+-- +-- Maintainer  : Simon Meier <iridcode@gmail.com>+-- Stability   : experimental+-- Portability : tested on GHC only+--+-- Benchmarking IO output speed of writing a string in Utf8 encoding to a file.+module Utf8IO (main)  where++import           Control.Monad+import           Control.Exception (evaluate)++import qualified Codec.Binary.UTF8.Light as Utf8Light++import           Data.Char (chr)+import           Data.Time.Clock+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Lazy.UTF8 as Utf8String+import qualified Data.Text.Lazy          as TL+import qualified Data.Text.Lazy.Encoding as TL++import           System.IO+import           System.Environment++import           Blaze.ByteString.Builder           +import           Blaze.ByteString.Builder.Internal (defaultBufferSize)+import qualified Blaze.ByteString.Builder.Char.Utf8 as Blaze+++-- | Write using the standard text utf8 encoding function built into 'base'.+writeUtf8_base :: String -> FilePath -> IO ()+writeUtf8_base cs file = +    withFile file WriteMode $ \h -> do+        hSetEncoding h utf8+        hPutStr h cs++-- | Write using utf8 encoding as provided by the 'blaze-builder' library.+writeUtf8_blaze :: String -> FilePath -> IO ()+writeUtf8_blaze cs file = L.writeFile file $ toLazyByteString $ Blaze.fromString cs++-- | Write using utf8 encoding as provided by the 'text' library.+writeUtf8_text :: TL.Text -> FilePath -> IO ()+writeUtf8_text tx file = L.writeFile file $ TL.encodeUtf8 tx++-- | Write using utf8 encoding as provided by the 'utf8-string' library.+writeUtf8_string :: String -> FilePath -> IO ()+writeUtf8_string cs file = L.writeFile file $ Utf8String.fromString cs++-- | Write using utf8 encoding as provided by the 'utf8-light' library. Note+-- that this library only allows encoding the whole string as a strict+-- bytestring. That might make it unusable in some circumstances.+{-# NOINLINE writeUtf8_light #-}+writeUtf8_light :: String -> FilePath -> IO ()+writeUtf8_light cs file = Utf8Light.writeUTF8File file cs+++main :: IO ()+main = do+    [how, len, file] <- getArgs+    let blocksize = 32000+        block     = map chr [0..blocksize]+        n         = read len+        cs        = take n $ cycle $ block+        tx        = TL.pack cs+    writer <- case how of+        "base"        -> return $ writeUtf8_base cs+        "blaze"       -> return $ writeUtf8_blaze cs+        "utf8-string" -> return $ writeUtf8_string cs++        -- utf8-light is missing support for lazy bytestrings => test 100 times+        -- writing a 100 times smaller string to avoid out-of-memory errors.+        "utf8-light"  -> return $ \f -> sequence_ $ replicate 100 $ +                                        writeUtf8_light (take (n `div` 100) cs) f++        "via-text"    -> do return $ writeUtf8_text tx++        -- Here, we ensure that the text tx is already packed before timing.+        "text"        -> do _ <- evaluate (TL.length tx) +                            return $ writeUtf8_text tx+        _             -> error $ "unknown writer '" ++ how ++ "'"+    t <- timed_ $ writer file+    putStrLn $ how ++ ": " ++ show t++------------------------------------------------------------------------------+-- Timing+------------------------------------------------------------------------------++-- | Execute an IO action and return its result plus the time it took to execute it.+timed :: IO a -> IO (a, NominalDiffTime)+timed io = do+  t0 <- getCurrentTime+  x <- io+  t1 <- getCurrentTime+  return (x, diffUTCTime t1 t0)++-- | Execute an IO action and return the time it took to execute it.+timed_ :: IO a -> IO NominalDiffTime+timed_ = (snd `liftM`) . timed+
blaze-builder.cabal view
@@ -1,5 +1,5 @@ Name:                blaze-builder-Version:             0.2.1.4+Version:             0.3.0.0 Synopsis:            Efficient buffered output.  Description: