Skip to content

Instantly share code, notes, and snippets.

@etrepum
Last active January 2, 2016 13:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save etrepum/8312165 to your computer and use it in GitHub Desktop.
Save etrepum/8312165 to your computer and use it in GitHub Desktop.
Haskell Control.Concurrent client
.cabal-sandbox
dist
cabal.sandbox.config
client
server
*.hi
*.o

These benchmarks were performed on a virtualized Rackspace Cloud server running Ubuntu 13.10, with the "30GB Performance" flavor (8 vCPUs, 30GB RAM, 2.5 Gb/s network)

GHC (compiled from git sha ec4af3f):

$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.7.20140107

Server

$ ./dist/build/server-threaded/server-threaded 5055
Listening on 5055

Client benchmarks

$ time ./dist/build/client-single/client-single 127.0.0.1 5055 1000 100000
Connecting to 127.0.0.1 5055
conc: 1000 reqs: 100000 batchSize: 25
100000 successes, 0 errors in 9.024822 s
min/max/avg request time: 91.07590 us / 7.280918 s / 20.69674 ms
11081 r/s

real    0m9.058s
user    0m2.183s
sys     0m6.874s

$ time ./dist/build/client-threaded/client-threaded 127.0.0.1 5055 1000 100000                                                          
Connecting to 127.0.0.1 5055
conc: 1000 reqs: 100000 batchSize: 25
100000 successes, 0 errors in 8.152756 s
min/max/avg request time: 166.8930 us / 7.014485 s / 47.34623 ms
12266 r/s

real    0m8.200s
user    0m11.045s
sys     0m12.633s

For reference, here's how the same code worked with GHC 7.6.3

$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.6.3

Server

$ ./dist/build/server-single/server-single 5055         
Listening on 5055

Client

$ time ./dist/build/client-single/client-single 127.0.0.1 5055 1000 100000
Connecting to 127.0.0.1 5055
conc: 1000 reqs: 100000 batchSize: 25
100000 successes, 0 errors in 8.877150 s
min/max/avg request time: 58.88939 us / 1.060330 s / 1.040994 ms
11265 r/s

real	0m8.940s
user	0m2.200s
sys	0m6.735s

$ time ./dist/build/client-threaded/client-threaded 127.0.0.1 5055 1000 100000
Connecting to 127.0.0.1 5055
conc: 1000 reqs: 100000 batchSize: 25
100000 successes, 0 errors in 14.46206 s
min/max/avg request time: 190.9733 us / 7.046602 s / 137.7901 ms
6915 r/s

real	0m14.545s
user	0m17.072s
sys	0m20.840s
{-# Language OverloadedStrings #-}
import Network.Socket
( AddrInfo(..), SocketType(..), SocketOption(..)
, getAddrInfo, socket, connect, close, defaultProtocol
, setSocketOption
)
import Network.Socket.ByteString (recv, sendAll)
import Control.Exception (handle, IOException)
import System.Environment (getArgs)
import Control.Concurrent
( MVar, forkIO, newEmptyMVar, putMVar, takeMVar )
import Control.Monad (void, when, replicateM)
import Data.Either (partitionEithers)
import Data.List (intercalate)
import Text.Printf (printf)
import Data.IORef (IORef, atomicModifyIORef', newIORef)
import Criterion.Measurement (time, time_, secs)
main :: IO ()
main = do
(host, port, conc0, reqs) <- fmap parse getArgs
-- arbitrary decision to split work into ~4 batches
let conc = min conc0 reqs
batchSize = max 1 (reqs `div` conc `div` 4)
putStrLn $ "Connecting to " ++ host ++ " " ++ port
printf "conc: %d reqs: %d batchSize: %d\n" conc reqs batchSize :: IO ()
(servAddr:_) <- getAddrInfo Nothing (Just host) (Just port)
(diff, results) <- time $ process servAddr conc reqs batchSize
let (errs, succs) = partitionEithers results
numSuccs = length succs
numErrs = length errs
succTime = sum succs
succAvg = succTime / fromIntegral numSuccs
putStrLn $ unwords
[show numSuccs, "successes,", show numErrs, "errors in", secs diff]
mapM_ print errs
when (numSuccs > 0) $ do
putStrLn $ "min/max/avg request time: " ++
intercalate " / " (map secs [minimum succs, maximum succs, succAvg])
putStrLn $ show (round (fromIntegral reqs / diff) :: Int) ++ " r/s"
parse :: [String] -> (String, String, Int, Int)
parse [h,p,conc,reqs] = (h, p, read conc, read reqs)
parse _ = error "usage client host port concurrency requests"
process :: AddrInfo -> Int -> Int -> Int -> IO [Either IOException Double]
process servAddr conc reqs batchSize = do
counter <- newIORef reqs
outputs <- replicateM conc (forkThread counter)
concat `fmap` mapM takeMVar outputs
where
forkThread counter = do
output <- newEmptyMVar
void . forkIO $ processThread socketAction batchSize counter output
return output
socketAction = do
sock <- socket (addrFamily servAddr) Stream defaultProtocol
setSocketOption sock NoDelay 1
connect sock (addrAddress servAddr)
sendAll sock "GET /\r\n\r\n"
void $ recv sock 1024
close sock
processThread :: IO () -- ^ action to run
-> Int -- ^ batch size
-> IORef Int -- ^ number of times to run the action
-> MVar [Either IOException Double] -- ^ results
-> IO ()
processThread action batchSize counter output = while []
where
while acc = do
steps <- atomicModifyIORef' counter decr
if steps > 0
then replicateM steps go >>= while . (++acc)
else putMVar output acc
decr v = let r = min v batchSize in (v - r, r)
go = handle (return . Left) (Right `fmap` time_ action)
-- Initial concurrent-network-demo.cabal generated by cabal init. For
-- further documentation, see http://haskell.org/cabal/users-guide/
name: concurrent-network-demo
version: 0.1.0.0
-- synopsis:
-- description:
license: MIT
license-file: LICENSE
author: Bob Ippolito
maintainer: bob@redivi.com
-- copyright:
category: Concurrency
build-type: Simple
-- extra-source-files:
cabal-version: >=1.10
executable client-threaded
main-is: client.hs
build-depends: base >=4.6,
vector,
stm,
criterion,
network
default-language: Haskell2010
Ghc-Options: -O2 -Wall -rtsopts -with-rtsopts=-N -threaded
executable client-single
main-is: client.hs
build-depends: base >=4.6,
vector,
stm,
criterion,
network
default-language: Haskell2010
Ghc-Options: -O2 -Wall -rtsopts
executable server-threaded
main-is: server.hs
build-depends: base >=4.6,
network
default-language: Haskell2010
Ghc-Options: -O2 -Wall -rtsopts -with-rtsopts=-N -threaded
executable server-single
main-is: server.hs
build-depends: base >=4.6,
network
default-language: Haskell2010
Ghc-Options: -O2 -Wall -rtsopts
This is the MIT license.
Copyright (c) 2014 Bob Ippolito.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import Network (listenOn, PortID(..))
import Network.Socket
( Socket, SocketOption(..), accept, close, setSocketOption )
import Network.Socket.ByteString (recv, sendAll)
import System.Environment (getArgs)
import Control.Concurrent (forkIO)
import Control.Monad (forever)
main :: IO ()
main = do
(n:_) <- getArgs
let nn = PortNumber (fromIntegral (read n :: Int))
putStrLn $ "Listening on " ++ n
sock <- listenOn nn
setSocketOption sock NoDelay 1
serve sock
serve :: Socket -> IO ()
serve sock = forever $
accept sock >>= forkIO . process . fst
process :: Socket -> IO ()
process sock = do
setSocketOption sock NoDelay 1
recv sock 1024 >>= sendAll sock >> close sock
import Distribution.Simple
main = defaultMain
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment