Portability | tested on GHC only |
---|---|
Stability | experimental |
Maintainer | Simon Meier <iridcode@gmail.com> |
- data Write = Write !Int (Ptr Word8 -> IO ())
- fromWrite :: Write -> Builder
- fromWriteSingleton :: (a -> Write) -> a -> Builder
- fromWrite1List :: (a -> Write) -> [a] -> Builder
- fromWrite2List :: (a -> Write) -> [a] -> Builder
- fromWrite4List :: (a -> Write) -> [a] -> Builder
- fromWrite8List :: (a -> Write) -> [a] -> Builder
- fromWrite16List :: (a -> Write) -> [a] -> Builder
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
provided by
Blaze.ByteString.Builder.Word creates a writeWord8
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.
Creating builders from Write
abstractions
fromWrite :: Write -> BuilderSource
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.)
fromWriteSingleton :: (a -> Write) -> a -> BuilderSource
fromWrite1List :: (a -> Write) -> [a] -> BuilderSource
fromWrite2List :: (a -> Write) -> [a] -> BuilderSource
fromWrite4List :: (a -> Write) -> [a] -> BuilderSource
fromWrite8List :: (a -> Write) -> [a] -> BuilderSource