LINUX.ORG.RU

помощи по conduit тхреад

 conduit, ,


0

4

Вот смотри, лор. Хочу осилить кондуиты, для этого пишу простую программу.

Суть:

  • читаем файл в виде текста Data.Text.Lazy (размер может быть очень большой)
  • считаем, сколько раз в тексте встречается каждая последовательность из 3х символов (условно). В тексте «aoaoa» последовательность «aoa» встречается 2 раза, а «oao» только один. То есть нужно брать сначала символы с 0-2 потом с 1-3 и так далее и засовывать их в словарь как ключ, значение словаря - число попаданий.
  • Сгенерировать на основе словаря последовательность из n «слов» (3х символьных кусков из п.2) так, чтобы вероятность попадания каждого такого слова была тем выше, чем чаще оно встречается в оригинальном тексте

Цель: феерически скоростная программа, кушающая файлы на субсветовых скоростях (ограничение по диску). Само собой программа должна работать в постоянном объеме памяти с высокой эффективностью, чтобы GC не сходил с ума.

Хочу заюзать хэш таблицы из judy (отговорите меня кто-нибудь).

Понимаю, что кондуиты тут не нужны, так как ленивый текст и так читает кусками по 64к, и по мере потребления текста в IO действиях над judy он будет подгружаться.

Если есть более лучшая идея для вкуривания кондуитов, то будет рассмотрена.

Наркомания какая-то... Кто все эти слова?

DELIRIUM ☆☆☆☆☆
()

tcp-server!

Само собой программа должна работать в постоянном объеме памяти с высокой эффективностью, чтобы GC не сходил с ума.

засовывание новых последовательностей в память в O(1) по памяти, это как?

так чтоли? (чуть правильнее было бы не leftover-ом закидывать назад, а хранить буффер, как сделано в takeWhile в Data.Conduit.Binary)

ДЗ :) сделать фильтрацию «левых символов»

import Data.Conduit
import Data.Conduit.Binary
import Data.Conduit.List as CL
import Data.Conduit.Text
import Data.Text as T
import Data.HashMap.Strict as M

import Control.Monad
import Control.Monad.Trans
import System.Environment
import Debug.Trace 

main = do
    [fp] <- getArgs 
    runResourceT $ 
        sourceFile fp $= decode utf8 
                      $$ byN 3 =$= collect M.empty 
                      =$ (await >>= lift . lift . print)
    where
      byN n = do
        mx <- await
        case mx of 
          Nothing -> return ()
          Just x  -> 
            let t' = T.take n x
            in  yield t' >> when (T.length x > 1) (leftover (T.drop 1 x)) >> byN n
      collect e = do
        mx <- await
        case mx of
          Nothing -> yield e
          Just x  -> collect (M.insertWith (+) x 1 e)
qnikst ★★★★★
()
Ответ на: комментарий от qnikst

tcp-server

разве реализаций уже не 100500 ? Лил tcp самому реализовывать тоже ?

И по коду вопрос: а разве это не strict будет ? если файл 20G, например, он его не весь зачитает при первом await в byN ? Там же strict Text, а это тупо вектор чаров.

s9gf4ult ★★
() автор топика
Ответ на: комментарий от s9gf4ult

разве реализаций уже не 100500 ? Лил tcp самому реализовывать тоже ?

зато с кондуитами и всякими странными юз-кейсами где нужены форки-объединения труб можно разобраться, можешь взять какой-нить bittorrent/rsync/ftp/.. сервер, которого нету (или нету на кондуитах)

Strict но не всё сразу. Смотри там, sourceFile читает чанками, - получили чанк, его декодировали в текст, этот кусок текста в обработку (размер равен размеру чанка в байтах), при обработке делим на тройки (+размер отступа и длины на каждую тройку) и кладём в мапу, если это новое значение, то чанк остаётся в памяти, если старое - удаляется (это исправляется если делать yield . T.copy), и потом всё по новой.

qnikst ★★★★★
()
Ответ на: комментарий от qnikst

То есть sourceFile возвращает строгие байтстринги кусками ? И вызов mx <- await в byN делается много раз на каждый кусок ? А как тогда происходит декодирование, ведь utf-8 символы не постоянного размера, и как быть с тройками, которые режутся разрывами между чанков ?

Или decode utf8 возвращает цельный Text из всего файла ? Но тогда он строгий и при первом обращении к символам из Text будет прочитан весь файл, ведь Text - это строгий массив символов, и для вычисления Text потребуются все данные. Или я не прав и Text устроен сложнее ?

s9gf4ult ★★
() автор топика
Ответ на: комментарий от s9gf4ult

То есть sourceFile возвращает строгие байтстринги кусками ?

да

И вызов mx <- await в byN делается много раз на каждый кусок ?

да

А как тогда происходит декодирование, ведь utf-8 символы не постоянного размера, и как быть с тройками, которые режутся разрывами между чанков ?

оно хранит тот символ который не доделался и прибавляет его к след чанку. Во всяком случае так оно должно быть.

как быть с тройками, которые режутся разрывами между чанков ?

делать тоже самое, что в предыдущем пункте

qnikst ★★★★★
()
Ответ на: комментарий от qnikst

https://github.com/s9gf4ult/conduplay/blob/tag2/conduplay.hs

Вот. Сделал нечто похожее на правду. В Data.Text нету операции вырезания куска текста так что пришлось через drop/take вытаскивать данные.

byN' t count

Тут t - кусок отрезанного текста, который нужно добавить к следующему чанку если такой есть.

Короче, программа валится со StackOverflow. Почему - определить не могу. Как определить причину переполнения ? Опция -hd в профайле кучи выдает невнятные компиляторные названия клозов. Куча полна санков ессесно.

s9gf4ult ★★
() автор топика
Ответ на: комментарий от quasimoto

Блин, ну я понимаю что у тебя там либтелепатия последней версии, но как узнал то ? Не буду же я на лор бегать каждый раз ...

s9gf4ult ★★
() автор топика
Ответ на: комментарий от s9gf4ult

Так оно завелось? Я попробовал на 30MB файле - вроде работает.

Как узнал - хвостовая рекурсия в духе Scheme из-за нестрогой стратегии вычисления это красная тряпка в хаскеле. То есть код типа foldl:

f ... TermCase ... = ReturnSomething ...
f ... recCase ... = f ... (transform ... recCase ...) ...

не будет работать как обычный компактный по памяти цикл как это происходит в строгих языках с поддержкой TCO в реализации. Вместо этого он будет накапливать санки. Поэтому нужно явно выставить строгость относительно трансформируемых аккумуляторов:

f ... TermCase ... = ReturnSomething ...
f ... recCase ... = let next = transform ... recCase ... in next `seq` f ... next ...

или так

f ... TermCase ... = ReturnSomething ...
f ... recCase ... = f ... $! (transform ... recCase ...) ...

или так

{-# LANGUAGE BangPatterns #-}

f ... TermCase ... = ReturnSomething ...
f ... !recCase ... = f ... (transform ... recCase ...) ...

аналогично нужно следить за строгостью в типах данных и за возможными утечками и задержками из-за lazy IO (hFlush, те же BangPatterns, DeepSeq, Strategies и т.п. - http://users.aber.ac.uk/afc/stricthaskell.html).

Ещё там byN так же рекурсивна, но она, видимо, не такая активная как collect (но тоже можно выставить строгость по аккумуляторам с помощью BangPatterns).

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

Я спрашивал не совсем это. В других языках я могу получить стектрейс и узнать в каком месте кода произошла ошибка. Если код на подобие этого хеловорда, то можно и наугад пытаться выявить дефектный код, а если проект большой, то что тогда ?

А про свертку я сразу понял почему санки копятся. Просто НЕ УВИДЕЛ !

s9gf4ult ★★
() автор топика
Ответ на: комментарий от s9gf4ult

Просто НЕ УВИДЕЛ !

Ты наверно не те опции у хип профайлера используешь.

Нужно собирать всё с поддержкой профилирования, то есть, например

$ cabal-dev install --enable-library-profiling --enable-executable-profiling

в *.cabal в ghc-options нужно включить опции которые сделают все функции SCC, так чтобы они были видимы при heap cost профилировании. Например:

  ghc-options: -O2 -fforce-recomp -rtsopts -auto-all -caf-all

И потом использовать rts опции для heap cost и retainer профилирования:

$ cabal-dev/bin/conduplay file +RTS -hc
$ cabal-dev/bin/conduplay file +RTS -hr

а -hd и прочие показывают уже другую информацию.

Тогда должны получиться такие картинки:

http://tryimg.com/3/condu.png

http://tryimg.com/3/cfcf.png

И после добавления строгости в collect:

http://tryimg.com/3/cbjb.png

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

У меня есть приложение (сервер игры), которое течёт непонятно где, приходится раз в месяц перезапускать, а то оно совсем невменяемо начинает себя вести: память кушает гигабайтами, процессорное время по 20-30%. Такие графики я делал, но так и не понял, что и откуда там лишнее берётся. Интересно, что простейшими стресс-тестами не воспроизводится ситуация, даже если часами на максимальной скорости эмулировать подключения-отключения десятков тысяч игроков. В продакшене же медленно но верно что-то где-то подтекает. Ты бы не мог помочь, пожалуйста?

unC0Rr ★★★★★
()
Ответ на: комментарий от quasimoto

-auto-all -caf-all

Можно было сократить ответ до такого, спасибочки.

s9gf4ult ★★
() автор топика
Ответ на: комментарий от unC0Rr

Интересно, что простейшими стресс-тестами не воспроизводится ситуация

Я попробовал их запустить в profiling окружении - с gameServer/stresstest2.hs сервер как раз начинает есть память гигабайтами и профайлер рисует listenLoop/receiveWithBufferLoop (много, особенно в retainer), processAction (меньше). Так и должно быть?

В продакшене же медленно но верно что-то где-то подтекает. Ты бы не мог помочь, пожалуйста?

Про медленно но верно не знаю - по коду трудно что-то сказать, я бы пытался всё-таки воспроизвести - даже на реальном сервере, если это возможно (time slice по -i выставить, например).

quasimoto ★★★★
()
Ответ на: комментарий от quasimoto

Так и должно быть?

Нет, не должно быть. Может, он просто захлёбывается от запросов.

unC0Rr ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.