[Haskell-beginners] IPerf in Haskell doesn't perform
Thomas Bach
thbach at students.uni-mainz.de
Thu Nov 7 15:40:47 UTC 2013
Hi list,
I had to do a little benchmarking of a network connection between two
servers and I thought “Well, Haskell seemed pretty awesome, let's use it
to accomplish the task.” So I spent a couple of days with the Haskell
library and finally got the following (working!) code (see below). Now
my questions:
1) Although being a beautiful language, my code seems to be ugly. Any
hints to improve this? My problem is (I think) that I simply cannot get
rid of the IO Monad and the only way I know how to deal with this is by
using lots of 'do's which doesn't help at all to get rid of the monads.
(Probably the imperative background problem…) :)
2) The program I wrote doesn't perform. It sended around 33 MBytes in 10
seconds over a connection where iperf (the one packaged by Ubuntu,
written in C) was able send 650 MBytes. Where can I improve performance?
I'm especially interested in the low hanging fruits, which are easy to
understand as a Haskell novice. ;)
3) When I run this program, I get the following Error (both, on the
client, as well as on the server):
$./iperf -c localhost
"Running as client connecting to localhost"
Stack space overflow: current size 8388608 bytes.
Use `+RTS -Ksize -RTS' to increase it.
enlarging the stack space avoids it, but how can I avoid it in the code?
I guess that the stack space overflow comes from the recursive call to
receiveData on the server and sendIntermediateAndFinalData in the
client. I thought that in the way I implemented it, these functions are
tailor recursive and therefor not prone to stack overflows!?! Anyway, I
didn't check that, so maybe the error is somewhere else…
Regards,
Thomas Bach.
PS: This is the code pasted “As Quotation”. I couldn't find another way
to get the code into Thunderbird w/o messing up line breaks and the
like. I additionally added the code as an attachment. I hope this is ok
– time to get a new MUA!
> import Data.Time (DiffTime, utctDayTime, getCurrentTime)
> import Foreign (ForeignPtr, Int64, Ptr, mallocForeignPtr, withForeignPtr)
> import Foreign.Storable (sizeOf, peek, poke)
> import GHC.IO.Handle (Handle, BufferMode(NoBuffering), hClose, hGetBuf, hSetBuffering, hPutBuf)
> import Network (Socket, HostName, PortID(PortNumber), PortNumber, accept, connectTo, listenOn, withSocketsDo)
> import System.Console.GetOpt (ArgOrder(Permute), ArgDescr(..), OptDescr(..), getOpt, usageInfo)
> import System.Environment (getArgs)
>
> data Flag = Client String | Server
> deriving Show
>
> -- Command line arguments for server and client mode.
>
> options :: [OptDescr Flag]
> options =
> [ Option ['c'] ["client"] (ReqArg Client "HOST") "Connect to host as client."
> , Option ['s'] ["server"] (NoArg Server) "Run as server."
> ]
>
> perfOpts :: [String] -> IO [Flag]
> perfOpts argv =
> case getOpt Permute options argv of
> ([], [], []) -> ioError (userError ("At least one Option is needed." ++ usageInfo "" options))
> (o, [], []) -> return o
> (_, _, errs) -> ioError (userError (concat errs ++ usageInfo "" options))
>
> -- Some constants
>
> port = PortNumber 8456
> type Trans = Int64
> numBytes = sizeOf (0 :: Trans)
> inital = -128 :: Trans
> intermediate = 0 :: Trans
> final = 127 :: Trans
>
> makePtr = mallocForeignPtr :: IO (ForeignPtr Trans)
>
> -- The client connects to the server, initializes the connection
> -- by sending initial, sends then for the amount of at least
> -- 10 secs as much intermediate (0's) as possible and finally
> -- sends final.
>
> runClient :: String -> IO ()
> runClient host = do
> print ("Running as client connecting to " ++ host)
> hdl <- initClient host port
> ptr <- makePtr
> fillInitial ptr
> withForeignPtr ptr (sendBuf hdl)
> fillIntermediate ptr
> curTime <- fmap utctDayTime getCurrentTime
> (sentBytes, time) <- sendIntermediateAndFinal ptr hdl curTime 10 0
> print ("Sent " ++ show sentBytes ++ " Bytes in " ++ show time ++ " seconds.")
> hClose hdl
>
> initClient :: HostName -> PortID -> IO Handle
> initClient host port = withSocketsDo $ do
> hdl <- connectTo host port
> hSetBuffering hdl NoBuffering
> return hdl
>
> sendIntermediateAndFinal :: ForeignPtr Trans -> Handle -> DiffTime -> DiffTime -> Int -> IO (Int, DiffTime)
> sendIntermediateAndFinal ptr hdl start duration sent = do
> curTime <- fmap utctDayTime getCurrentTime
> if (curTime - start) > duration
> then do fillFinal ptr
> withForeignPtr ptr (sendBuf hdl)
> finishedTime <- fmap utctDayTime getCurrentTime
> return (sent + numBytes, finishedTime - start)
> else do withForeignPtr ptr (sendBuf hdl)
> sendIntermediateAndFinal ptr hdl start duration (sent + numBytes)
>
> sendBuf :: Handle -> Ptr Trans -> IO ()
> sendBuf hdl buf = hPutBuf hdl buf numBytes
>
> fillPtr :: Trans -> ForeignPtr Trans -> IO ()
> fillPtr num ptr = withForeignPtr ptr (\p -> poke p num)
>
> fillInitial = fillPtr inital
> fillIntermediate = fillPtr intermediate
> fillFinal = fillPtr final
>
> -- The server simply accepts connections, receives what it can get
> -- and adds up the received bytes and transmission time.
>
> runServer :: IO ()
> runServer = do
> print "Running as server."
> initServer port >>= handleConnection
>
> initServer :: PortID -> IO Socket
> initServer port = withSocketsDo $ listenOn port
>
> handleConnection :: Socket -> IO ()
> handleConnection socket = do
> (hdl, host, port) <- acceptConnection socket
> print ("Connection from " ++ host)
> ptr <- makePtr
> (num, bytes) <- receive ptr hdl
> curTime <- fmap utctDayTime getCurrentTime
> if num == inital
> then do (received, time) <- receiveData ptr hdl curTime 0
> print ("Received " ++ show received ++ " in " ++ show time ++ " seconds.")
> hClose hdl
> handleConnection socket
> else do print ("ERR: Expected " ++ show inital ++ " got " ++ show num)
> hClose hdl
> handleConnection socket
>
> acceptConnection :: Socket -> IO (Handle, HostName, PortNumber)
> acceptConnection socket = do
> (hdl, host, port) <- accept socket
> hSetBuffering hdl NoBuffering
> return (hdl, host, port)
>
> receiveData :: ForeignPtr Trans -> Handle -> DiffTime -> Int -> IO (Int, DiffTime)
> receiveData ptr hdl started received = do
> (num, bytes) <- receive ptr hdl
> if num /= final
> then receiveData ptr hdl started (received + bytes)
> else do curTime <- fmap utctDayTime getCurrentTime
> return (received + bytes, curTime - started)
>
> receive :: ForeignPtr Trans -> Handle -> IO (Trans, Int)
> receive ptr hdl = do
> withForeignPtr ptr (\p -> hGetBuf hdl p numBytes)
> num <- withForeignPtr ptr peek
> return (num, numBytes)
>
> -- The main routine decides whether to run as server or client.
>
> main :: IO ()
> main = do
> opts <- getArgs >>= perfOpts
> if length opts /= 1
> then ioError (userError ("Too many arguments!" ++ usageInfo "" options))
> else
> case head opts of
> Server -> runServer
> Client host -> runClient host
-------------- next part --------------
A non-text attachment was scrubbed...
Name: iperf.hs
Type: text/x-haskell
Size: 4927 bytes
Desc: not available
URL: <http://www.haskell.org/pipermail/beginners/attachments/20131107/1e7ba573/attachment.hs>
More information about the Beginners
mailing list