{-# LANGUAGE OverloadedStrings, BangPatterns, RecordWildCards, MagicHash #-} import Data.ByteString (ByteString) import Criterion.Main import Data.BufferBuilder import Control.Monad scheme :: ByteString scheme = "http" host :: ByteString host = "example.com" path :: ByteString path = "the/path/goes/here" buildURL :: Int -> ByteString buildURL times = runBufferBuilder $ do replicateM_ times $ do -- Sadly, if you look at the generated code, there are many -- continuations (thus indirect jumps, thus inefficient stack -- traffic) here. Even though the test strings are constant, -- they become ByteString CAFs, and are tag-checked on every -- use. However, appendBS followed by appendChar8 are folded -- into the same generated function. appendBS scheme appendBS "://" appendBS host appendChar8 '/' appendBS path appendChar8 '?' appendBS "key" appendChar8 '=' appendBS "value" appendChar8 '?' appendBS "otherkey" appendChar8 '=' appendBS "othervalue" appendChar8 '#' appendBS "hashyhashyhashy" buildURLLiterals :: Int -> ByteString buildURLLiterals times = runBufferBuilder $ do replicateM_ times $ do -- literals avoid the CAFs for ByteString constants unsafeAppendLiteralN 4 "http"# unsafeAppendLiteralN 3 "://"# unsafeAppendLiteralN 11 "example.com"# appendChar8 '/' unsafeAppendLiteralN 18 "the/path/goes/here"# appendChar8 '?' unsafeAppendLiteralN 3 "key"# appendChar8 '=' unsafeAppendLiteralN 5 "value"# appendChar8 '?' unsafeAppendLiteralN 8 "otherkey"# appendChar8 '=' unsafeAppendLiteralN 10 "othervalue"# appendChar8 '#' unsafeAppendLiteralN 15 "hashyhashyhashy"# data Record = Record { f1 :: !ByteString , f2 :: !ByteString , f3 :: !ByteString , f4 :: !ByteString , f5 :: !ByteString , f6 :: !ByteString } encodeType :: Record -> ByteString encodeType !(Record{..}) = runBufferBuilder $ do -- Because the record elements are strict, all of these appendBS -- calls are emitted in one long Cmm/x86 function. Can't do much -- better than that. :) However, if you look closely, the -- BufferWriter handle (accessed through ReaderT) is reloaded from -- the stack after all appendBS. In the future, GHC could realize -- it's always the same value and only load it once. appendBS f1 appendBS f2 appendBS f3 appendBS f4 appendBS f5 appendBS f6 recordValue :: Record recordValue = Record { f1 = "the wheels on the bus go round and round" , f2 = "round and round, round and round" , f3 = "the seats on the bus go up and down" , f4 = "up and down, up and down" , f5 = "the doors on the bus go open and shut" , f6 = "open and shut, open and shut" } main :: IO () main = defaultMain [ bench "buildURL" $ nf buildURL 10 , bench "buildURLLiterals" $ nf buildURLLiterals 10 , bench "encodeRecord" $ nf encodeType recordValue ]