Плохая производительность сети 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 МБ/с, поэтому это приемлемо.
Ответы
Ответ 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