openlang

foldl'을 (total, count) 쌍에 썼는데도 상주 메모리가 1GB가 넘어요?

회사 일(모바일 앱)로 쓰는 작은 도구가 있는데, 분석 export 파일인 세션 이벤트 CSV를 읽어서 평균 세션 길이를 출력해요. 약 600만 줄에 대략 300MB예요. 답은 제대로 나와요. 그런데 전체 파일로 돌리면 오래된 Intel MacBook의 팬이 세게 돌고, +RTS -s는 최대 상주 메모리(maximum residency)를 1GB 훌쩍 넘게 보고해요.

처음엔 foldl로 짰다가 foldl'로 바꿨는데 최대 메모리는 거의 그대로였어요. -O2는 이미 cabal 파일에 들어 있고, 그것도 별 차이가 없었어요.

meanMillis :: [Session] -> Double
meanMillis ss
  | n == 0    = 0
  | otherwise = fromIntegral total / fromIntegral n
  where
    (total, n) = foldl' step (0 :: Int, 0 :: Int) ss
    step (t, c) s = (t + sessMillis s, c + 1)

그리고 main에서는 이렇게 해요:

contents <- readFile (optPath opts)
let sessions = mapMaybe parseLine (drop 1 (lines contents))

Session은 String 하나와 Int 하나를 가진 평범한 레코드고, 화면별 필터는 그냥 filter예요.

첫 번째로 의심하는 건 readFile과 lines라서 hGetLine 루프를 생각해 봤는데, 아직 시도는 안 했어요. 파일을 lazy하게 소비하고 각 줄을 파싱한 뒤 버린다면 strict fold로 충분해야 하는데 왜 아닌지 모르겠어요. 이 머신에서는 프로파일링 빌드가 너무 느려서, 거의 -s 숫자만 보고 짐작하고 있어요.

메모리가 실제로 어디로 가는 걸까요?

영어에서 번역원문 보기

이어지는 대화 1

서로의 근거를 따라
  1. 고유 링크

    다른 도구에서 저도 같은 함정에 걸렸어요. foldl'은 튜플 자체만 강제하고 안에 든 값은 강제하지 않아서, t + ...와 c + 1이 thunk로 쌓여요. step에 bang pattern을 쓰니 해결됐어요: step (!t, !c) s. (BangPatterns pragma가 필요해요, 역시나.) 바로 해결됐어요.

    영어에서 번역원문 보기

같은 질문의 다른 갈래

  1. 빈 리덕션은 항등원을 돌려준다

    빈 벡터를 리덕션한다. APL은 답한다. 프리미티브마다 항등원이 있다. 더하기는 0. 곱하기와 논리곱은 1. ⌈/⍬는 인터프리터가 가진 가장 작은 음수다. 호출하는 쪽에 특수 처리가 없다. 리덕션 전에 길이를 확인하지도 않는…

    답글 3최근 활동 apl
  2. 매크로 대부분이 const와 static이 됐다

    Nim은 매크로를 쓰기 쉽게 해 준다. 그게 문제다. macro에 손대기 전에 더 싼 도구부터 본다. const는 매크로를 돌리는 바로 그 VM이 컴파일 시점에 평가한다. 그래서 평범한 proc으로 만든 조회 테이블은 그냥…

    답글 3최근 활동 nim
  3. it, _1, 그리고 여전히 쓰는 블록 이름

    Ruby 3.4에 블록 매개변수를 가리키는 it이 들어왔습니다. 덕분에 users.map { it.name }은 거의 문장처럼 읽힙니다. 저는 _1보다 이쪽이 좋습니다. 그쪽은 늘 파서가 흘리고 간 무언가처럼 보였습니다. P…

    답글 0최근 활동 ruby