代码之家  ›  专栏  ›  技术社区  ›  ondra

haskell网络性能差

  •  12
  • ondra  · 技术社区  · 15 年前

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

    它所做的:它打开一个TUN设备;它将自己绑定到一个UDP端口上,启动两个线程(forkIO,但是由于fdRead的原因,它是用-threaded编译的)。我没有使用tuntap包,完全是自己在Haskell做的。

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

    问题1:在这个配置中,fdRead返回String,我使用了接受String的Network.Socket函数。我在本地系统上做了一个配置(一些iptables魔术),我可以在本地主机上运行15MB/s,程序基本上运行在100%的CPU上。太慢了。我能做些什么来提高表现吗?

    问题2:我必须在发送的数据包前面加上一些内容;但是sendmanny网络函数只接受ByteString;从Fd读取返回字符串。转换相当慢。在TUN设备上转换到Handle似乎不够好。。。。

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

    有办法解决这些问题吗?还是我必须回到C。。。?我所做的应该主要是零拷贝操作-我是否使用了错误的库来实现它?

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

    我所做的: -向MVar提交时:

    a `seq` putMVar mvar a
    

    这完全有助于解决内存泄漏问题。

    • 改为ByteString;现在只使用“读/写”而不进行进一步处理时,我得到42MB/s。C版本的速度大约为56MB/s,因此这是可以接受的。
    3 回复  |  直到 15 年前
        1
  •  22
  •   Carl    15 年前

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

    您想独占使用ByteString。ByteString IO函数实际上尽可能使用零拷贝IO。尤其是看看 network-bytestring 关于黑客的包裹。它包含所有网络库的版本,这些网络库经过优化,可以有效地使用ByteString。

        2
  •  5
  •   Tener    15 年前

    下面是两个示例程序:客户机和服务器。使用GHC 7.0.1和network-2.3,我在我全新的双核笔记本电脑上通过环回获得了超过7500 Mbps的速度(约占总CPU使用率的90%)。我不知道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
    
        3
  •  5
  •   stites James    10 年前

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

    推荐文章