Загадочная странность в простой программе на Haskell
От: Rtveliashvili Denys Великобритания  
Дата: 08.10.09 20:32
Оценка:
Доброе время суток!

Недавно я обнаружил абсолютно загадочное для меня поведение в даже самых простых программах на 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)
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.