wireform-core-0.2.0.0: bench/SendBuilderBench.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{- | Benchmark: sendBuilder (via ByteString) vs sendBuilderDirect (ring sink).
Uses an in-memory send transport backed by a magic ring so the
benchmark isolates builder-to-ring staging cost without any I/O.
-}
module Main where
import Criterion.Main
import Data.ByteString qualified as BS
import Data.IORef
import Data.Word
import Foreign.Ptr (Ptr)
import Wireform.Builder
import Wireform.Ring.Internal (ringBase, ringMask, ringSize, withMagicRing)
import Wireform.Transport.Send
------------------------------------------------------------------------
-- In-memory send transport (no I/O, immediate drain)
------------------------------------------------------------------------
{- | Build a 'SendTransport' over a magic ring that acts as an
instant drain: every 'sendPublishHead' immediately advances the
tail to match, so the ring never fills. This isolates the
builder staging cost.
-}
mkMemorySendTransport
:: Ptr Word8
-> Int
-> Int
-> IO SendTransport
mkMemorySendTransport base sz msk = do
headRef <- newIORef (0 :: Word64)
tailRef <- newIORef (0 :: Word64)
let publish h = do
writeIORef headRef h
writeIORef tailRef h
pure
SendTransport
{ sendRingBase = base
, sendRingSize = sz
, sendRingMask = msk
, sendLoadTail = readIORef tailRef
, sendLoadHead = readIORef headRef
, sendPublishHead = publish
, sendWaitSpace = \_ -> do
tl <- readIORef tailRef
pure (SendSpaceAvailable tl)
, sendFlush = pure ()
, sendShutdownWrite = pure ()
, sendClose = pure ()
}
------------------------------------------------------------------------
-- Builders of various sizes
------------------------------------------------------------------------
-- 64-byte payload: small protocol header / Kafka produce ack
smallBuilder :: Builder
smallBuilder =
mconcat
[ word32BE 0x00000001
, word32BE 0x00000040
, byteStringCopy (BS.replicate 56 0xAA)
]
-- 256-byte payload: typical Kafka produce record
mediumBuilder :: Builder
mediumBuilder =
mconcat
[ word32BE 0x00000002
, word32BE 0x00000100
, byteStringCopy (BS.replicate 248 0xBB)
]
-- 1 KiB payload: HTTP response header block
kiloBuilder :: Builder
kiloBuilder =
mconcat
[ word32BE 0x00000003
, word32BE 0x00000400
, byteStringCopy (BS.replicate 1016 0xCC)
]
-- 4 KiB payload: typical HTTP response body chunk
fourKBuilder :: Builder
fourKBuilder =
mconcat
[ word32BE 0x00000004
, word32BE 0x00001000
, byteStringCopy (BS.replicate 4088 0xDD)
]
-- 16 KiB payload: large message, exercises ring overflow
sixteenKBuilder :: Builder
sixteenKBuilder =
mconcat
[ word32BE 0x00000005
, word32BE 0x00004000
, byteStringCopy (BS.replicate 16376 0xEE)
]
-- 64 KiB payload: stresses ring chunking
sixtyFourKBuilder :: Builder
sixtyFourKBuilder =
mconcat
[ word32BE 0x00000006
, word32BE 0x00010000
, byteStringCopy (BS.replicate 65528 0xFF)
]
------------------------------------------------------------------------
-- Main
------------------------------------------------------------------------
main :: IO ()
main =
withMagicRing (256 * 1024) $ \ring -> do
let !base = ringBase ring
!sz = ringSize ring
!msk = ringMask ring
t <- mkMemorySendTransport base sz msk
let benchPair name builder =
bgroup
name
[ bench "direct (RingSink)" $ nfIO (sendBuilderDirect t builder)
, bench "via ByteString (old)" $ nfIO (sendBuilderViaByteString t builder)
]
defaultMain
[ benchPair "64 B" smallBuilder
, benchPair "256 B" mediumBuilder
, benchPair "1 KiB" kiloBuilder
, benchPair "4 KiB" fourKBuilder
, benchPair "16 KiB" sixteenKBuilder
, benchPair "64 KiB" sixtyFourKBuilder
]