Haskell - 线程时缓慢的套接字连接

时间:2013-06-05 20:42:38

标签: multithreading performance sockets networking haskell

我刚刚开始使用Haskell,现在我专注于网络。我跟着一些 教程和源代码示例将一个非常简单的echo服务器组合在一起:

main = withSocketsDo $ do
    forkIO $ acceptor 8080    
    print "Server running ... " >> getLine >>= print


tcpSock :: IO Socket
tcpSock = socket AF_INET Stream 0

acceptor :: PortNumber -> IO ()
acceptor port = do

    -- Setup server socket
    sock <- tcpSock
    setSocketOption sock ReuseAddr 1
    bindSocket sock (SockAddrInet port iNADDR_ANY)
    listen sock 50

    -- Start with zero index
    loop sock 0
        where
        loop sock sockId = do
            -- Accept socket
            (nextSock, addr) <- accept sock

            -- Setup the socket for performance
            (_, handle) <- setupClient nextSock

            -- Run client in own thread
            forkIO $ do
                -- Get a stream of bytes
                stream <- BS.hGetContents handle

                -- Echo the first received char
                BS.hPut handle $ BS.take 1 stream

                -- Kill the socket
                SIO.hClose handle


            -- Accept next client
            loop sock (sockId + 1)


setupClient :: Socket -> IO (Socket, SIO.Handle)
setupClient sock = do
        -- Disable nagle
        setSocketOption sock NoDelay 1

        -- Disable buffering
        hdl <- socketToHandle sock SIO.ReadWriteMode
        SIO.hSetBuffering hdl SIO.NoBuffering

        return (sock, hdl)

现在,我已经使用ab-Tool测试了代码来对服务器进行基准测试。代码已编译 使用-O2和-threaded并使用+ RTS -N启动程序以使用多个OS线程。

代码为每个客户端创建一个新的轻量级线程,据我记忆的是这些线程 非常便宜,因为它们是由一堆真正的操作系统线程安排的。

运行该工具后,结果为:

ab -n 10000 -c 1000 http://localhost:8080/ ~500 - 1600 req / sec 是的,它有时会在500到1600之间发生变化!

起初我认为好,不错。然后我运行程序没有“+ RTS -N”,结果几乎是每一个 时间~20000 req / sec。

显然,线程会严重影响性能,但为什么呢? 我的猜测是,IO经理在处理大量连接时做得非常糟糕。

BTW:我使用的是Ubuntu 13.04和ghc 7.6,但是我已经在Windows 8下测试了代码,这给我带来了更糟糕的结果,但我认为IO管理器已经针对linux进行了调整,这是有道理的。

我在这里做些蠢事吗?我知道,这个例子非常简单,但显然出现了问题。

此致 克里斯

1 个答案:

答案 0 :(得分:1)

好吧,我认为我已经解决了这个问题,但我仍然不确定错误在哪里。

我现在正在使用Network包,因此accept例程是基于句柄的。我试过这个是因为我在几次测试后发现了内存泄漏。

这种方式我立刻解决了两个问题,因为现在线程没有区别。 我真的不知道为什么会这样,但基于句柄的impl。更简单,显然更快/更安全。

也许这有助于其他人遇到同样的问题。