Performance haskell网络性能差

Performance haskell网络性能差,performance,networking,haskell,Performance,Networking,Haskell,我正在编写一些“类似openvpn”的东西,并认为这将是一个很好的候选人,以提高我的Haskell知识。但是,我遇到了相当严重的性能问题 它的作用:打开一个TUN设备;它在一个UDP端口上绑定自身,启动2个线程(forkIO,但是由于fdRead而使用-threaded编译)。我没有使用tuntap软件包,完全是自己在Haskell中使用的 线程1:从tun设备读取数据包(fdRead)。使用UDP套接字发送。 线程2:从UDP套接字读取数据包(recv);将其发送到tun设备(fdWrite)

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

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

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

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

问题2:我将不得不在我发送的数据包中预先添加一些内容;然而,sendmanny网络函数只接受ByteString;从Fd读取返回字符串。转换相当慢。转换为Handle似乎在TUN设备上工作得不够好

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

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

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

我所做的: -在进行MVar时,是否:

a `seq` putMVar mvar a
这对内存泄漏非常有帮助

  • 改为ByteString;现在,如果只使用“读/写”,而不进行进一步的处理,我将获得42MB/s。C版本的速度大约为56MB/s,因此这是可以接受的

    • 字符串速度较慢。真的,真的,真的很慢。这是一个cons单元格的单链列表,每个单元格包含一个unicode字符。将一个字符写入套接字需要将每个字符转换为字节,将这些字节复制到数组中,然后将该数组交给系统调用。这其中哪一部分听起来像你想做的事


      您希望以独占方式使用ByteString。ByteString IO函数实际上尽可能使用零拷贝IO。特别是看看黑客软件包。它包含所有网络库的版本,这些网络库都经过优化,可以有效地使用ByteString。

      Carl回答的前两个问题是正确的。关于最后一个,考虑使用.

      下面是两个示例程序:客户机和服务器。使用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
      
      --------------------
      --客户端程序--
      --------------------
      模块主要在哪里
      将限定数据.ByteString作为C导入
      导入网络套接字隐藏(recv)
      导入Network.Socket.ByteString(recv)
      导入系统.IO
      进口管制
      main::IO()
      主=带锁止SDO$
      
      devNull介意我问你为什么不使用tuntap软件包吗?(我是维护者……所以我很好奇。)我正在考虑使用“TUN”部分,并认为我会利用Haskell的“Handle”部分;结果不是这样的。在我看来,我可能最终会使用它,并使用TAP方式(并不是说有太大的区别),因为它返回ByteString,这可能会加快速度;能够使用标准SockAddr(而不是Word32)设置IP/netmask将是非常受欢迎的:)yipe!我刚刚看到concurrent strict将事物评估为标准形式(即deepseqs),而不是文档中所说的“头部标准形式”(即seqs)。回到懒惰的MVAR,自己强制使用seq进行评估可能是一个巨大的胜利。Heap应该保持懒惰以获得适当的摊销性能。我也发现了这一点,尽管我第一次是通过“错误地”键入rnf deepseq实例来实现的。将lazy MVAR与seq一起使用肯定更干净。请注意,如果您升级到最新的网络包,network bytestring现在已被折叠到其中!我也不知道。谢谢你的提醒。我正在使用network.bytestring;现在我甚至把我的“tun”电话转换成了ByteString。现在我有32MB/s,这在我看来仍然很糟糕。我将尝试tuntap包,但我认为这不会解决这个问题……在C代码中,我获得的最大速度是56MB/s。在哈斯克尔,我得到42分;这可能可以通过使用“buf”函数和一些内置魔法来调整,但是这可能是一个可以接受的差异。哦,我完全忽略了这个问题。我认为这个包有点过分了,但潜在的问题是当堆被修改时,结构没有被强制,这个包肯定会修复它;我使用UDP传输IP数据包,因此recv大小几乎固定为~1500。