Created
October 14, 2013 23:15
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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