2016-09-06 10 views
1

나는 이것을 Haskell과 파이프 라이브러리의 초보자라고 말하고, 무엇이 test 함수에서이 프로그램의 높은 메모리 사용을 일으키는 지 알고 싶다. . deepseq 사용하지 않는 특히 내가 최종 결과까지 데 MyRecord 값의 축적을보고 있어요 testr1 값을 생성하는 배에서이 Haskell 프로그램의 메모리 사용 이해하기

이 생산된다. ~ 500000 줄/230 MB의 샘플 데이터 세트에서 메모리 사용량은 1.5GB를 초과하여 증가합니다.

r2 값을 생성하는 폴드는 상수 메모리에서 실행됩니다.

내가 이해하고자하는 것입니다 :

최초의 배에 내 메모리 값의 빌드를 일으키는, 왜 그것을 해결하는 것 deepseq를 사용 할 수 무엇

1)? 항상 무작위로 물건을 던져서 deepseq을 사용하여 일정한 메모리 사용량을 얻었지만 왜 작동 하는지를 알고 싶습니다. 을 사용하지 않고 상수 메모리 사용을 달성 할 수 있습니까?

2). 두 번째 폴드가 다른 경우 동일한 문제가 발생하지 않는 이유는 무엇입니까?

튜플 대신 정수로만 작업한다면 Pipees.Prelude에서 내장 함수 sum을 사용할 수 있지만 결국에는 구문 분석 오류가 포함 된 두 번째 요소를 처리하려고합니다.

{-# LANGUAGE OverloadedStrings #-} 
{-# LANGUAGE FlexibleContexts #-} 
{-# LANGUAGE ScopedTypeVariables #-} 

module Test where 

import   Control.Arrow 
import   Control.DeepSeq 
import   Control.Monad 
import   Data.Aeson 
import   Data.Function 
import   Data.Maybe 
import   Data.Monoid 
import   Data.Text (Text) 

import   Pipes 
import qualified Pipes.Aeson as PA (DecodingError(..)) 
import qualified Pipes.Aeson.Unchecked as PA 
import qualified Pipes.ByteString as PB 
import qualified Pipes.Group as PG 
import qualified Pipes.Parse as PP 
import qualified Pipes.Prelude as P 

import   System.IO 
import   Control.Lens 
import qualified Control.Foldl as Fold 

data MyRecord = MyRecord 
    { myRecordField1 :: !Text 
    , myRecordField2 :: !Int 
    , myRecordField3 :: !Text 
    , myRecordField4 :: !Text 
    , myRecordField5 :: !Text 
    , myRecordField6 :: !Text 
    , myRecordField7 :: !Text 
    , myRecordField8 :: !Text 
    , myRecordField9 :: !Text 
    , myRecordField10 :: !Int 
    , myRecordField11 :: !Text 
    , myRecordField12 :: !Text 
    , myRecordField13 :: !Text 
    } deriving (Eq, Show) 

instance FromJSON MyRecord where 
    parseJSON (Object o) = 
    MyRecord <$> o .: "field1" <*> o .: "field2" <*> o .: "field3" <*> 
    o .: "field4" <*> 
    o .: "field5" <*> 
    o .: "filed6" <*> 
    o .: "field7" <*> 
    o .: "field8" <*> 
    o .: "field9" <*> 
    (read <$> o .: "field10") <*> 
    o .: "field11" <*> 
    o .: "field12" <*> 
    o .: "field13" 
    parseJSON x = fail $ "MyRecord: expected Object, got: " <> show x 

instance ToJSON MyRecord where 
    toJSON _ = undefined 

test :: IO() 
test = do 
    withFile "some-file" ReadMode $ \hIn 
    {- 

     the pipeline is composed as follows: 

     1 a producer reading a file with Pipes.ByteString, splitting chunks into lines, 
     and parsing the lines as JSON to produce tuples of (Maybe MyRecord, Maybe 
     ByteString), the second element being an error if parsing failed 

     2 a pipe filtering that tuple on a field of Maybe MyRecord, passing matching 
     (Maybe MyRecord, Maybe ByteString) downstream 

     3 and a pipe that picks an Int field out of Maybe MyRecord, passing (Maybe Int, 
     Maybe ByteString downstream) 

     pipeline == 1 >-> 2 >-> 3 

     memory profiling indicates the memory build up is due to accumulation of 
     MyRecord "objects", and data types comprising their fields (mainly 
     Text/ARR_WORDS) 

    -} 
    -> do 
    let pipeline = f1 hIn >-> f2 >-> f3 
    -- need to use deepseq to avoid leaking memory 
    r1 <- 
     P.fold 
     (\acc (v, _) -> (+) <$> acc `deepseq` acc <*> pure (fromMaybe 0 v)) 
     (Just 0) 
     id 
     (pipeline :: Producer (Maybe Int, Maybe PB.ByteString) IO()) 
    print r1 
    hSeek hIn AbsoluteSeek 0 
    -- this works just fine as is and streams in constant memory 
    r2 <- 
     P.fold 
     (\acc v -> 
      case fst v of 
      Just x -> acc + x 
      Nothing -> acc) 
     0 
     id 
     (pipeline :: Producer (Maybe Int, Maybe PB.ByteString) IO()) 
    print r2 
    return() 
    return() 

f1 
    :: (FromJSON a, MonadIO m) 
    => Handle -> Producer (Maybe a, Maybe PB.ByteString) m() 
f1 hIn = PB.fromHandle hIn & asLines & resumingParser PA.decode 

f2 
    :: Pipe (Maybe MyRecord, Maybe PB.ByteString) (Maybe MyRecord, Maybe PB.ByteString) IO r 
f2 = filterRecords (("some value" ==) . myRecordField5) 

f3 :: Pipe (Maybe MyRecord, d) (Maybe Int, d) IO r 
f3 = P.map (first (fmap myRecordField10)) 

filterRecords 
    :: Monad m 
    => (MyRecord -> Bool) 
    -> Pipe (Maybe MyRecord, Maybe PB.ByteString) (Maybe MyRecord, Maybe PB.ByteString) m r 
filterRecords predicate = 
    for cat $ \(l, e) -> 
    when (isNothing l || (predicate <$> l) == Just True) $ yield (l, e) 

asLines 
    :: Monad m 
    => Producer PB.ByteString m x -> Producer PB.ByteString m x 
asLines p = Fold.purely PG.folds Fold.mconcat (view PB.lines p) 

parseRecords 
    :: (Monad m, FromJSON a, ToJSON a) 
    => Producer PB.ByteString m r 
    -> Producer a m (Either (PA.DecodingError, Producer PB.ByteString m r) r) 
parseRecords = view PA.decoded 

resumingParser 
    :: Monad m 
    => PP.StateT (Producer a m r) m (Maybe (Either e b)) 
    -> Producer a m r 
    -> Producer (Maybe b, Maybe a) m() 
resumingParser parser p = do 
    (x, p') <- lift $ PP.runStateT parser p 
    case x of 
    Nothing -> return() 
    Just (Left _) -> do 
     (x', p'') <- lift $ PP.runStateT PP.draw p' 
     yield (Nothing, x') 
     resumingParser parser p'' 
    Just (Right b) -> do 
     yield (Just b, Nothing) 
     resumingParser parser p' 
+0

확인 haskell 태그 정보 섹션을 참조 하시고, 바이너리를 컴파일하고 실행하는 방법을 게시 하시길 바랍니다. – jberryman

+1

왜냐하면'seq (그냥 정의되지 않음) =()'하지만'seq (undefined :: Int)() = undefined' – user2407038

+1

'forceMaybe Nothing = Nothing; forceMaybe x @ (Just! _) = x'. – dfeuer

답변

3

docs for Pipes.foldl에서 언급했듯이 배수는 엄격합니다. 그러나 엄격함은 WHNF - weak head normal form에만 적용됩니다. implemented with $!입니다. WHNF는 Int와 같이 간단한 유형을 완전히 평가하기에 충분하지만 Maybe Int과 같이 더 복잡한 유형 인 을 완전히 평가할만큼 강하지 않습니다.

예로 - 모든 요소의 요약 첫 번째 경우에, 가변 acc

main1 = do 
    let a = 3 + undefined 
     b = seq a 10 
    print b    -- error: Exception: Prelude.undefined 

main2 = do 
    let a = Just (3 + undefined) 
     b = seq a 10 
    print b    -- no exception 

큰 썽크의 Just이다. 각 반복에서 변수 accJust a에서 Just (a+b)에서 Just (a+b+c) 등으로 변경됩니다. 추가 은 접히는 동안 수행되지 않고 맨 끝에서만 수행됩니다. 대용량 메모리 사용은이 증가하는 합계 을 메모리에 저장함으로써 발생합니다.

두 번째 경우에 합계는 $!에 의해 각 반복이 단순 Int.

deepseq를 사용 외에 당신은 또한 사용할 수 있습니다 force :

force x = x `deepseq` x 

하고 완전히 함수의 인수 평가하는 것이다 패턴을 만들 수 있습니다 ViewPatterns과 함께 mentioned in the deepseq docs, 같은 : 밖으로

{-# LANGUAGE ViewPatterns #-} 

... 
P.fold 
    (\(force -> !acc) (v,_) -> (+) <$> acc <*> pure (fromMaybe 0 v)) 
    (Just 0) 
    ... 
+0

@ErikR 고맙습니다. 폴드 누적기에있는 그 썽크가 쌓이면 MyRecord가 출시되지 않을 것이라고 생각합니다. (http://stackoverflow.com/questions/39354139/understanding-memory-usage- of-this-haskell-program # comment66042269_39354139)? – ppb

+1

예, 요약에 'myRecordField10' 호출을 통해 MyRecord 값에 대한 참조가 포함되어 있기 때문입니다. – ErikR