Доброе время суток!
Недавно я обнаружил абсолютно загадочное для меня поведение в даже самых простых программах на Haskell. Суть явления такова: есть программа, что подключается к некому серверу, тащит оттуда текст строчка за строчкой, каждую строчку "обрабатывает" и скидывает на стандартный вывод время "обработки". Так вот... Если "сервер" шлет эти строчки без задержки, то программа работает очень шустро. Но как только задержка между сообщениями возникает (даже если это порядка миллисекунды), то программа начинает заметно тормозить.
Для чистоты эксперимента я гарантирую, что обработка выполнится, путем if блока с проверкой длины строки, и не вывожу результаты на stdout пока сервер не прикроет связь.
Потрясающе, но несмотря на кажущуюся изолированность кода, проводящего "обработку" очередной строки (в случае примера — просто 10 раз делается reverse), время выполнения отличается очень сильно. Я построил гистограммы и кажется что это самое время выполнения как бы умножилось на некоторый коэффициент. Причем он не зависит от интервалов времени между сообщениями, важно только то, равен этот интервал нулю или нет. И для случаев с 0 миллисекунд и 10 миллисекунд медианы получаются 21 и 37 микросекунд соответственно.
Пока что кажется что быть может планировщик задач как-то странно работает и если поток блокируется в попытке прочитать что-то из сокета — поток (или даже весь процесс) понижается в приоритете. Но даже это кажется неадекватным объяснением, т.к. вряд ли планировщик может так "мягко" притормозить фрагмент кода, что
выполняется несколько десятков микросекунд.
А как Вы думаете, что это может быть за штука?
--
P.S. Еще гонял эту штуку под профайлером. Толковых результатов получить не удалось. В случае с ненулевым интервалом времени между получаемыми строчками почти все время уходит в модуль MAIN, а все остальное — сплошные нули.
--
Ну а сама программа вот тут:
{-# LANGUAGE BangPatterns #-}
module Main where
import qualified Data.ByteString.Char8 as B
import System.Time
import System.IO
import Control.Exception hiding (catch)
import Control.Monad
import Data.Maybe
import Network
import Network.Socket
main = do
putStrLn "total"
s <- connectTo "localhost" $ PortNumber 4123
hSetBuffering s $ LineBuffering
hSetBuffering stdout $ LineBuffering
pts <- collectWhileAvailable $ mProcessNextLine s -- process the incoming lines and collect the results
hClose s
forM_ pts $ \pt -> putStrLn $ show pt -- show the result
collectWhileAvailable :: IO (Maybe a) -> IO [a]
collectWhileAvailable mnext = do
mvalue <- mnext
case mvalue of
Just value -> do
rest <- collectWhileAvailable mnext
return (value:rest)
Nothing -> return []
mProcessNextLine s = (liftM Just $ processNextLine s) `catch` \e -> return Nothing
-- reads a line of text from socket, reverses it several times
-- (just to spend some time, proportionally to the size of the line),
-- calculates the time spent and returns it
processNextLine :: Handle -> IO Int
processNextLine s = do
line <- B.hGetLine s
startT <- getClockTime -- start of "processing"
let line' = process line
if 0 == B.length line' -- this is just to force the evaluation of line'
then return 0
else do
endT <- getClockTime -- end of processing
let processingTime = diffClockTimes endT startT
let pt = (fromIntegral ((tdPicosec processingTime) `div` 1000000)) +
(fromIntegral ((tdSec processingTime) * 1000000))
return $! pt -- returning the time spent for "processing" in microseconds
process line = process' line 10
where
process' !line !n = if n == 0 then line else process' (B.reverse line) (n-1)