糟糕的haskell网络性能

Posted

技术标签:

【中文标题】糟糕的haskell网络性能【英文标题】:Poor haskell network performance 【发布时间】:2010-11-19 19:00:33 【问题描述】:

我正在编写一些“类似 open***”的东西,并认为它是提高我的 Haskell 知识的好选择。但是,我遇到了相当严重的性能问题。

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

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

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

问题 2:我必须在发送的数据包中添加一些内容;但是 sendMany 网络函数只需要 ByteString;从 Fd 读取返回字符串。转换很慢。使用 TUN 设备转换为 Handle 似乎效果不佳....

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

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

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

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

a `seq` putMVar mvar a

这对内存泄漏很有帮助。

更改为字节串;现在我只使用“读/写”而不进行进一步处理时得到 42MB/s。 C 版本的速度约为 56MB/s,因此这是可以接受的。

【问题讨论】:

介意我问你为什么不使用 tuntap 包吗? (我是维护者......所以我很好奇。) 我正在考虑使用“TUN”部分,并认为我会利用 Haskell 的“Handle”部分;事实并非如此。在我看来,我可能最终会使用它并使用 TAP 方式(没有太大区别)——因为它返回的 ByteString 可能会加快速度。 我将在 tuntap 包中添加注释;能够使用标准 SockAddr(而不是 Word32)设置 IP/网络掩码将非常受欢迎:) 耶!我刚刚看到并发严格将事物评估为正常形式(即 deepseqs),而不是文档声称的“头部正常形式”(即 seqs)。回到惰性 mvar 并自己强制使用 seq 进行评估可能是一个巨大的胜利。 Data.Heap 应该保持惰性以获得正确的摊销性能。 我也发现了这一点,尽管我首先是通过“错误地”输入 rnf deepseq 实例来做到这一点的。使用带有 seq 的惰性 mvar 肯定更干净。 【参考方案1】:

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

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

【讨论】:

注意,如果你升级到最新的网络包,network-bytestring现在已经被折叠进去了! 我也没有意识到这一点。感谢您的提醒。 我正在使用 network.bytestring;现在我什至将我的“tun”调用转换为 ByteString。现在我有 32MB/s 这在我看来仍然很糟糕。我会尝试 tuntap 包,但我认为这不会解决这个问题...... 在 C 代码中我得到最大 56MB/s。在haskell我得到42;这可能可以通过使用“buf”函数和一些就地魔法来调整,但这可能是一个可以接受的差异。【参考方案2】:

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

【讨论】:

我无法调整 recv 大小;我正在使用 UDP 传输 IP 数据包,因此 recv 大小几乎固定为 ~ 1500。【参考方案3】:

关于您的前两个问题,Carl 是正确的。关于你的最后一个,考虑使用the strict concurrency package。

【讨论】:

哦,我完全错过了这个问题。我觉得这个包有点矫枉过正,但根本问题是修改堆时不会强制结构,这个包肯定会修复它。

以上是关于糟糕的haskell网络性能的主要内容,如果未能解决你的问题,请参考以下文章

Linux服务器性能评估

Haskell FFI/C 的性能考虑?

前端性能优化

Haskell Data.Memocombinators 性能问题?

如何在 Haskell 中查找和修复由于 GC 导致的性能问题?

性能测试--1为什么进行性能测试