Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to process two long lines with constant memory?

Input file consists of two lines, each of which holds many numbers

1 2 3 4... 
5 6 7 8... 

I want to process each line's data, like this:

doSomething :: [Int] -> [Int] -> Int
doSomething [] y = 0 -- stop execution. I'm concerted in memory behavior only.
doSomething (x:xs) y = doSomething xs y
main = do
    inputdata <- getContents
    let (x:xs) = lines inputdata
        firstLine = map (read ::String->Int) $ words $ x
        secondLine = map (read ::String->Int) $ words $ head xs
    print $ doSomething firstLine secondLine

When I run this program, heap profiling shows like this:

If I don't use secondLine (xs) then this program runs with constant memory. Each entry of list firstLine is processed then discarded by GC.

  1. Why the consumed memory is so large? I see the amount of allocated memory is ~100MB, but actual input data size is 5MB.

  2. Does head xs force reading entire first line into memory, even secondLine is not used at all? And is this the main cause of memory increase of heap profiling graph?

  3. How can I process two lines with constant memory, like latter execution graph?

  4. If the answer to 3) depends on processing order, how can I process second line first, and then first line?

like image 335
Chul-Woong Yang Avatar asked Jan 22 '16 07:01

Chul-Woong Yang


People also ask

What is constant memory in GPU?

Constant memory is a read-only cache which content can be broadcasted to multiple threads in a block.

What is o1 memory?

It means that your solution's memory usage should be constant. Meaning it should require the same amount of memory whether the input list has 5 elements or 1 million elements.

What is global memory?

Global memory is the main memory space and it is used to share data between host and GPU. Local memory is a particular type of memory that can be used to store data that does not fit in registers and is private to a thread.

What is CUDA memory?

It is used for storing data that will not change over the course of kernel execution. It supports short-latency, high-bandwidth, read-only access by the device when all threads simultaneously access the same location. There is a total of 64K constant memory on a CUDA capable device. The constant memory is cached.


1 Answers

Q1) Why the consumed memory is so large? I see the amount of allocated memory is ~100MB, but actual input data size is 5MB.

String in Haskell is a type alias for [Char], so along the actual bytes, the compiler also has to keep a constructor, a pointer to the boxed character and a pointer to the next constructor for every character in the memory, results >10 times of memory usage than a C-style string. Even worse the text is stored multiple times in memory.

Q3) How can I process two lines with constant memory, like latter execution graph?
Q4) If the answer to Q3) depends on processing order, how can I process second line first, and then first line?

No you can't process the second line first since the lines function have to evaluate every byte in the first line to hit the '\n' newline character.

Q2) Does head xs force reading entire first line into memory, even secondLine is not used at all? And is this the main cause of memory increase of heap profiling graph?

It's not the head that prevents the first line from being GCed. The space leak still occurs if you adjust the type signature of doSomething and pass xs directly to it. The point is the (not optimizing) compiler won't know that secondLine isn't used before doSomething finally hits the first pattern, so the program keeps a reference to xs . BTW if compiled with -O2 your program runs with constant memory.

What caused the space leak of your program is mainly this line:

let (x:xs) = lines inputdata

When either x or xs is discarded, this let clause will be in-lined in the dumped Core. Only when both of them are referenced later the Core behaves strangely: it constructs a tuple, destruct it by pattern matching, then construct the two part into a tuple again, therefore by keeping a reference to secondLine the program in fact keeps a reference to a tuple (x, xs) so the first line will never be GCed.

Core with secondLine commented out:

Rec {
doSomething_rjH
doSomething_rjH =
  \ ds_d1lv y_alG ->
    case ds_d1lv of _ {
      [] -> I# 0;
      : x_alH xs_alI -> doSomething_rjH xs_alI y_alG
    }
end Rec }

main
main =
  >>=
    $fMonadIO
    getContents
    (\ inputdata_app ->
       print
         $fShowInt
         (doSomething_rjH
            (map
               (read $fReadInt)
               (words
                  (case lines inputdata_app of _ {
                     [] -> case irrefutPatError "a.hs:6:9-32|(x : xs)"# of wild1_00 { };
                     : x_auf xs_aug -> x_auf
                   })))
            ([])))

main
main = runMainIO main

Core with space leak:

Rec {
doSomething_rjH
doSomething_rjH =
  \ ds_d1ol y_alG ->
    case ds_d1ol of _ {
      [] -> I# 0;
      : x_alH xs_alI -> doSomething_rjH xs_alI y_alG
    }
end Rec }

main
main =
  >>=
    $fMonadIO
    getContents
    (\ inputdata_app ->
       -- *** Construct ***
       let {
         ds_d1op
         ds_d1op =
           case lines inputdata_app of _ {
             [] -> irrefutPatError "a.hs:6:9-30|x : xs"#;
             : x_awM xs_awN -> (x_awM, xs_awN)
           } } in
       -- *** Destruct ***
       let {
         xs_awN
         xs_awN = case ds_d1op of _ { (x_awM, xs1_XwZ) -> xs1_XwZ } } in
       let {
         x_awM
         x_awM = case ds_d1op of _ { (x1_XwZ, xs1_XwU) -> x1_XwZ } } in
       -- *** Construct ***
       let {
         ds1_d1oq
         ds1_d1oq = (x_awM, xs_awN) } in
       print
         $fShowInt
         -- *** Destruct ***
         (doSomething_rjH
            (map
               (read $fReadInt)
               (words (case ds1_d1oq of _ { (x1_Xx1, xs1_Xx3) -> x1_Xx1 })))
            (map
               (read $fReadInt)
               (words
                  (head (case ds1_d1oq of _ { (x1_Xx1, xs1_Xx3) -> xs1_Xx3 }))))))

main
main = runMainIO main

Replace the let clause by a case .. of clause will fix the space leak:

doSomething :: [Int] -> [Int] -> Int
doSomething [] _ = 0 -- stop execution. I'm concerted in memory behavior only.
doSomething (_:xs) y = doSomething xs y

main :: IO ()
main = do
  inputdata <- getContents
  case lines inputdata of
    x:xs -> do
      let
        firstLine = map (read ::String->Int) $ words x
        secondLine = map (read ::String->Int) $ words $ head xs
      print $ doSomething firstLine secondLine

The dumped Core. The "construct then destruct" pattern didn't happen this time:

Rec {
doSomething_rjG
doSomething_rjG =
  \ ds_d1o6 ds1_d1o7 ->
    case ds_d1o6 of _ {
      [] -> I# 0;
      : ds2_d1o8 xs_alG -> doSomething_rjG xs_alG ds1_d1o7
    }
end Rec }

main
main =
  >>=
    $fMonadIO
    getContents
    (\ inputdata_apn ->
       case lines inputdata_apn of _ {
         [] -> patError "a.hs:(8,3)-(13,46)|case"#;
         : x_asI xs_asJ ->
           print
             $fShowInt
             (doSomething_rjG
                (map (read $fReadInt) (words x_asI))
                (map (read $fReadInt) (words (head xs_asJ))))
       })

main
main = runMainIO main
like image 175
zakyggaps Avatar answered Oct 13 '22 03:10

zakyggaps