Skip to content

Instantly share code, notes, and snippets.

@danbst
Created October 14, 2013 23:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save danbst/6983872 to your computer and use it in GitHub Desktop.
Save danbst/6983872 to your computer and use it in GitHub Desktop.
Based upon http://www.reddit.com/r/haskell/comments/1o4m98/feedback_wanted_trying_to_write_a_good_receive/ Task is: read one line from a stream of ByteString chars. Done with explicit recursion and pipes (and with their ByteString.Builder versions). Included Criterion benchmarks.
import qualified Data.ByteString.Char8 as B
import Criterion.Main
import Data.IORef
import Data.Vector
import Data.ByteString.Char8 (ByteString)
import Control.Monad (forever)
import Pipes
import qualified Pipes.Prelude as P
import Data.Char (intToDigit)
import qualified Data.ByteString.Builder as Builder
import qualified Data.ByteString.Lazy.Char8 as L
import Data.Monoid
type Counter = Int -> IO Int
problemSize = 10000
makeCounter :: IO Counter
makeCounter = do
r <- newIORef 0
return (\i -> do modifyIORef r (+i)
readIORef r)
recv :: Counter -> IO B.ByteString
recv counter = do
i <- counter 1
if i == problemSize
then return eol
else return $ B.singleton $ intToDigit $ i `rem` 16
eol = B.singleton '\n'
-- 1. Explicit recursion
{-
mean: 485.4873 ms, lb 485.0373 ms, ub 485.9573 ms, ci 0.950
std dev: 2.341772 ms, lb 2.066285 ms, ub 2.704366 ms, ci 0.950
-}
recvLn :: Counter -> IO B.ByteString
recvLn counter = do
-- Receive one byte at a time.
first <- recv counter
rest <- if first == eol
then return B.empty
else recvLn counter
-- Once the recursion finishes, we glue the parts together.
return $ first `B.append` rest
-- 2. Pipes
ioGenerator :: IO a -> Producer a IO ()
ioGenerator gen = forever $ lift gen >>= yield
{-
mean: 344.9492 ms, lb 343.9391 ms, ub 347.1393 ms, ci 0.950
std dev: 7.296816 ms, lb 3.562015 ms, ub 13.62466 ms, ci 0.950
-}
recvLn_pipes :: Counter -> IO B.ByteString
recvLn_pipes counter = P.fold B.append B.empty id stream
where stream = ioGenerator (recv counter) >-> P.takeWhile (/= eol)
-- 3. Recursion with ByteString.Builder
{-
mean: 328.1883 ms, lb 327.6582 ms, ub 329.0083 ms, ci 0.950
std dev: 3.345775 ms, lb 2.391181 ms, ub 4.886398 ms, ci 0.950
-}
recvLn_builder :: Counter -> IO L.ByteString
recvLn_builder counter = fmap Builder.toLazyByteString go
where go = do
-- Receive one byte at a time.
first <- recv counter
rest <- if first == eol
then return (Builder.fromByteString B.empty)
else go
-- Once the recursion finishes, we glue the parts together.
return $ mappend (Builder.fromByteString first) rest
-- 4. Pipes with Builder
{-
mean: 324.7181 ms, lb 324.2780 ms, ub 325.2881 ms, ci 0.950
std dev: 2.560526 ms, lb 2.061868 ms, ub 3.703595 ms, ci 0.950
-}
recvLn_builderPipes :: Counter -> IO L.ByteString
recvLn_builderPipes counter = P.fold append empty Builder.toLazyByteString stream
where stream = ioGenerator (recv counter) >-> P.takeWhile (/= eol)
empty = Builder.fromByteString B.empty
append builder byteStr = mappend (Builder.fromByteString byteStr) builder
main :: IO ()
main = defaultMain
[ bench "get line (original - explicit recursion with `B.append`)" $
whnfIO $ run (makeCounter >>= recvLn) 5
, bench "get line (original - pipes with `B.append`)" $
whnfIO $ run (makeCounter >>= recvLn_pipes) 5
, bench "get line (bytestring builder - explicit recursion)" $
whnfIO $ run (makeCounter >>= recvLn_builder) 5
, bench "get line (bytestring builder - pipes)" $
whnfIO $ run (makeCounter >>= recvLn_builderPipes) 5
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment