performance - 如何处理具有恒定内存的两条长线?

标签 performance haskell lazy-evaluation

输入文件由两行组成,每行包含许多数字

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

我想处理每一行的数据,如下所示:
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

当我运行这个程序时,堆分析显示如下:


如果我不使用 secondLine ( xs ),那么这个程序会以恒定内存运行。
列表中的每个条目firstLine被 GC 处理然后丢弃。

  • 为什么消耗的内存这么大?
    我看到分配的内存量约为 100MB,但实际输入数据大小为 5MB。
  • 是否 head xs强制将整个第一行读入内存,
    甚至 secondLine根本不用?这是主要原因吗
    堆分析图的内存增加?
  • 如何处理具有恒定内存的两条线,例如后面的​​执行图?
  • 如果 3) 的答案取决于处理顺序,我怎样才能先处理第二行,然后再处理第一行?
  • 最佳答案

    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在 Haskell 中是 [Char] 的类型别名,因此在实际字节中,编译器还必须为内存中的每个字符保留一个构造函数、一个指向装箱字符的指针和一个指向下一个构造函数的指针,结果比 C 风格的字符串占用了 10 倍以上的内存。更糟糕的是,文本多次存储在内存中。

    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?



    不,你不能从 lines 开始处理第二行。函数必须评估第一行中的每个字节以命中 '\n' 换行符。

    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?



    这不是 head这可以防止第一行被 GCed。如果调整 doSomething 的类型签名,仍然会发生空间泄漏。并通过 xs直接到它。关键是(未优化的)编译器不会知道 secondLinedoSomething 之前未使用终于命中了第一个模式,所以程序保留了对 xs 的引用.顺便说一句,如果使用 -O2 编译,您的程序将使用常量内存运行。

    导致你程序空间泄漏的原因主要是这行:
    let (x:xs) = lines inputdata
    

    xxs被丢弃,这个 let 子句将内联在转储的核心中。只有当它们都被稍后引用时,Core 才会出现奇怪的行为:它构造一个元组,通过模式匹配来破坏它,然后再次将这两个部分构造成一个元组,因此保持对 secondLine 的引用。该程序实际上保留了对元组 (x, xs) 的引用所以第一行永远不会被GCed。

    带有 secondLine 的核心注释掉:
    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
    

    有空间泄漏的核心:
    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
    

    替换 let case .. of 的子句子句将修复空间泄漏:
    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
    

    被倾倒的核心。这次没有发生“先建后毁”的模式:
    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
    

    关于performance - 如何处理具有恒定内存的两条长线?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34941146/

    相关文章:

    haskell - 我自己定义严格的应用程序($!)不会产生相同的性能

    haskell - 从 ghci session 中的子目录导入(从 yesod 中的测试导入模块)

    haskell - 从 Haskell do 表示法转换 listDirectory 映射

    lazy-evaluation - Fancytree延迟加载: Make Ajax call each time it expands

    ruby - 导轨 4 : why is one way of rendering partials so much faster?

    html - Google 可以用缩小的 HTML 和 CSS 索引网站吗?

    functional-programming - OCaml 懒惰评估 : 'a lazy_t vs unit -> ' a

    java - 过滤功能不偷懒

    performance - ASP.NET Web API StreamContent 与 IIS 静态文件处理程序

    sql-server - SQL Server : Select in vs or?