Подтвердить что ты не робот

Плохая производительность сети haskell

Я программирую какую-то "openvpn-like" вещь и думал, что это будет хороший кандидат для улучшения моего знания Haskell. Однако я столкнулся с довольно серьезными проблемами с производительностью.

Что он делает: он открывает устройство TUN; он связывается с UDP-портом, запускает 2 потока (forkIO, однако скомпилированный с помощью -threaded из-за fdRead). Я не использовал пакет tuntap и сделал это сам полностью в Haskell.

поток 1: прочитайте пакет (fdRead) с устройства tun. Отправьте его с помощью UDP-сокета.
thread 2: прочитать пакет (recv) из сокета UDP; отправьте его на устройство tun (fdWrite)

Проблема 1: В этой конфигурации fdRead возвращает String, и я использовал функции Network.Socket, которые принимают String. Я сделал конфигурацию на локальной системе (магия iptables), и я могу запустить 15 МБ/с через localhost, программа запускается в основном на 100% процессоре. Это медленно. Я могу что-то сделать, чтобы улучшить производительность?

Проблема 2: мне нужно будет что-то добавить к пакетам, которые я отправляю; однако функция сети sendMany принимает только ByteString; чтение из Fd возвращает String. Конверсия довольно медленная. Преобразование в Handle, похоже, не работает достаточно хорошо с устройством TUN....

Проблема 3: Я хотел сохранить некоторую информацию в Data.Heap(функциональная куча) (мне нужно использовать "takeMin", и хотя для 3-х элементов это слишком много, это легко сделать:)). Поэтому я создал MVar и на каждом полученном пакете я вытащил кучу из MVar, обновил кучу с новой информацией и вернул ее inito MVar. Теперь вещь просто начинает есть МНОГО памяти. Наверное, потому что старые кучи не собирают мусор в скором времени/достаточно часто.?

Есть ли способ решить эти проблемы или мне нужно вернуться к C...? То, что я делаю, должно быть в основном законсервировано - я использую неправильные библиотеки для его достижения?

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

Что я сделал: - при установке на MVar, сделал:

a `seq` putMVar mvar a

Это отлично справилось с утечкой памяти.

  • изменено на ByteString; теперь я получаю 42 МБ/с при использовании только "чтения/записи" без дальнейшей обработки. Версия C составляет около 56 МБ/с, поэтому это приемлемо.
4b9b3361

Ответ 1

Строка медленная. Действительно, действительно, очень медленно. Это односвязный список cons-ячеек, каждый из которых содержит один символ Unicode. Запись одного в сокет требует преобразования каждого символа в байты, копирования этих байтов в массив и передачи этого массива в системный вызов. Какая часть этого звучит так, как вы хотите?:)

Вы хотите использовать только ByteString. Функции IO ByteString фактически используют OO с нулевой копией, где это возможно. Особенно посмотрите на network-bytestring пакет для взлома. Он содержит версии всех сетевых библиотек, оптимизированных для эффективной работы с ByteString.

Ответ 2

Карл прав в отношении ваших первых двух вопросов. О своем последнем, рассмотрите строгий пакет concurrency.

Ответ 3

Ниже приведены две примерные программы: клиент и сервер. Используя GHC 7.0.1 и network-2.3, я получил более 7500 Мбит/с по шлейфу, на моем довольно новом двухъядерном ноутбуке (~ 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