糟糕的haskell网络性能

时间:2010-11-19 19:00:33

标签: performance networking haskell

我正在编写一些'类似openvpn'的东西,并认为这将是一个很好的候选人来提高我的Haskell知识。但是,我遇到了相当严重的性能问题。

它做什么:它打开一个TUN设备;它将自己绑定在UDP端口上,启动2个线程(forkIO,但是由于fdRead而使用-thaded编译)。我没有使用过tuntap软件包,而是完全在Haskell中完成了。

线程1:从tun设备读取数据包(fdRead)。使用UDP套接字发送 线程2:从UDP套接字读取数据包(recv);将它发送到tun设备(fdWrite)

问题1:在这个配置中,fdRead返回String,我使用了接受String的Network.Socket函数。我在本地系统(一些iptables魔术)上进行了配置,我可以在localhost上运行15MB / s,程序基本上在100%CPU上运行。那很慢。我有什么办法可以改善表现吗?

问题2:我必须在我发送的数据包之前添加一些东西;但是sendMany网络函数只接受ByteString;从Fd读取返回String。转换速度很慢。使用TUN设备转换为Handle似乎不够好....

问题3:我想在Data.Heap(功能堆)中存储一些信息(我需要使用'takeMin',虽然对于3个项目来说太过分了,但很容易做到:))。所以我创建了一个MVar,并且在每个接收到的数据包上我从MVar中取出了Heap,用新信息更新了Heap并将它放回到init的MVar中现在只是开始吃大量的内存。可能是因为旧堆不会很快/频繁地收集垃圾......?

有没有办法解决这些问题,还是我必须回到C ......?我正在做的应该主要是zerocopy操作 - 我使用错误的库来实现它吗?

==================

我做了什么: - 当放到MVar时,做了:

a `seq` putMVar mvar a

这完全有助于内存泄漏。

  • 更改为ByteString;现在我只使用“读/写”时没有进一步处理就得到42MB / s。 C版本的速度大约为56MB / s,所以这是可以接受的。

3 个答案:

答案 0 :(得分:22)

字符串很慢。真的,非常非常慢。它是一个单独链接的cons单元列表,每个单元格包含一个unicode字符。将一个写入套接字需要将每个字符转换为字节,将这些字节复制到一个数组中,并将该数组交给系统调用。这听起来像你想做什么? :)

您想要独占使用ByteString。 ByteString IO函数实际上尽可能使用零拷贝IO。特别是关于hackage的network-bytestring包。它包含所有网络库的版本,这些库经过优化,可以与ByteString一起高效工作。

答案 1 :(得分:5)

关于你的前两个问题,卡尔是对的。关于您的上一个,请考虑使用the strict concurrency package

答案 2 :(得分:5)

以下是两个示例程序:客户端和服务器。使用GHC 7.0.1和network-2.3,在我的全新双核笔记本电脑(总CPU使用率约为90%)上,我在环回时获得了超过7500 Mbps的速度。我不知道UDP引入了多少开销,但不过这是一个很大的数字。

--------------------
-- Client program --
--------------------
module Main where

import qualified Data.ByteString as C
import Network.Socket hiding (recv)
import Network.Socket.ByteString (recv)

import System.IO
import Control.Monad

main :: IO ()
main = withSocketsDo $
    do devNull <- openFile "/dev/null" WriteMode
       addrinfos <- getAddrInfo Nothing (Just "localhost") (Just "3000")
       let serveraddr = head addrinfos
       sock <- socket (addrFamily serveraddr) Stream defaultProtocol
       connect sock (addrAddress serveraddr)
       forever $ do
         msg <- recv sock (256 * 1024) -- tuning recv size is important!
         C.hPutStr devNull msg
       sClose sock


--------------------
-- Server program --
--------------------
module Main where

-- import Control.Monad (unless)
import Network.Socket hiding (recv)
import qualified Data.ByteString.Lazy as S
import Network.Socket.ByteString.Lazy (
                                       --recv, 
                                       sendAll)

main :: IO ()
main = withSocketsDo $
       do addrinfos <- getAddrInfo
                        (Just (defaultHints {addrFlags = [AI_PASSIVE]}))
                        Nothing (Just "3000")
          let serveraddr = head addrinfos
          sock <- socket (addrFamily serveraddr) Stream defaultProtocol
          bindSocket sock (addrAddress serveraddr)
          listen sock 1
          (conn, _) <- accept sock
          talk conn
          sClose conn
          sClose sock

     where
       talk :: Socket -> IO ()
       talk conn = sendAll conn $ S.repeat 7