从GHC探查器

时间:2016-10-14 13:26:49

标签: haskell profiling ghc

我试图从GHC分析器中理解。有一个相当简单的应用程序,它使用werqlens-aeson库,在学习GHC分析时,我决定使用它。

使用不同的选项(time工具,+RTS -p -RTS+RTS -p -h)我获得了完全不同的内存使用量。拥有所有这些数字后,我现在完全迷失了,试图了解正在发生的事情以及应用程序实际使用的内存量。

这种情况让我想起了亚瑟布洛赫的一句话:“一个有手表的男人知道现在是什么时候。一个有两只手表的男人从来都不确定。”

请你,建议我,我如何阅读所有这些数字,以及每个数字的含义是什么。

以下是数字:

time -l报告 19M

#/usr/bin/time -l ./simple-wreq
...
        3.02 real         0.39 user         0.17 sys
  19070976  maximum resident set size
         0  average shared memory size
         0  average unshared data size
         0  average unshared stack size
     21040  page reclaims
         0  page faults
         0  swaps
         0  block input operations
         0  block output operations
        71  messages sent
        71  messages received
      2991  signals received
        43  voluntary context switches
      6490  involuntary context switches

围绕 92M 使用+RTS -p -RTS举报报告。虽然它说“总分配”对我来说似乎很奇怪,像这样一个简单的应用程序可以分配和释放91M

# ./simple-wreq +RTS -p -RTS      
# cat simple-wreq.prof
        Fri Oct 14 15:08 2016 Time and Allocation Profiling Report  (Final)

           simple-wreq +RTS -N -p -RTS

        total time  =        0.07 secs   (69 ticks @ 1000 us, 1 processor)
        total alloc =  91,905,888 bytes  (excludes profiling overheads)

COST CENTRE                             MODULE                          %time %alloc

main.g                                  Main                             60.9   88.8
MAIN                                    MAIN                             24.6    2.5
decodeLenient/look                      Data.ByteString.Base64.Internal   5.8    2.6
decodeLenientWithTable/fill             Data.ByteString.Base64.Internal   2.9    0.1
decodeLenientWithTable.\.\.fill         Data.ByteString.Base64.Internal   1.4    0.0
decodeLenientWithTable.\.\.fill.\       Data.ByteString.Base64.Internal   1.4    0.1
decodeLenientWithTable.\.\.fill.\.\.\.\ Data.ByteString.Base64.Internal   1.4    3.3
decodeLenient                           Data.ByteString.Base64.Lazy       1.4    1.4


                                                                                                             individual     inherited
COST CENTRE                                              MODULE                            no.     entries  %time %alloc   %time %alloc

MAIN                                                     MAIN                              443           0   24.6    2.5   100.0  100.0
 main                                                    Main                              887           0    0.0    0.0    75.4   97.4
  main.g                                                 Main                              889           0   60.9   88.8    75.4   97.4
   object_                                               Data.Aeson.Parser.Internal        925           0    0.0    0.0     0.0    0.2
    jstring_                                             Data.Aeson.Parser.Internal        927          50    0.0    0.2     0.0    0.2
   unstream/resize                                       Data.Text.Internal.Fusion         923         600    0.0    0.3     0.0    0.3
   decodeLenient                                         Data.ByteString.Base64.Lazy       891           0    1.4    1.4    14.5    8.1
    decodeLenient                                        Data.ByteString.Base64            897         500    0.0    0.0    13.0    6.7
....

+RTS -p -hhp2ps向我展示了以下图片和两个数字:标题中的 114K 以及图表上 1.8Mb 周围的内容。 Memory Profiling

而且,以防万一,这是应用程序:

module Main where

import Network.Wreq
import Control.Lens
import Data.Aeson.Lens
import Control.Monad

main :: IO ()
main = replicateM_ 10 g
  where 
    g = do
        r <- get "http://httpbin.org/get"
        print $ r ^. responseBody
                   . key "headers"
                   . key "User-Agent"
                   . _String

更新1:感谢所有人提供了令人难以置信的良好反应。正如所建议的那样,我添加了+RTS -s输出,因此整个图片都会为每个阅读它的人构建。

#./simple-wreq +RTS -s
...
     128,875,432 bytes allocated in the heap
      32,414,616 bytes copied during GC
       2,394,888 bytes maximum residency (16 sample(s))
         355,192 bytes maximum slop
               7 MB total memory in use (0 MB lost due to fragmentation)

                                     Tot time (elapsed)  Avg pause  Max pause
  Gen  0       194 colls,     0 par    0.018s   0.022s     0.0001s    0.0022s
  Gen  1        16 colls,     0 par    0.027s   0.031s     0.0019s    0.0042s

更新2 :可执行文件的大小:

#du -h simple-wreq
63M     simple-wreq

2 个答案:

答案 0 :(得分:7)

  

一个有手表的男人知道现在几点了。一个有两只手表的男人永远不会确定。

啊,但两只手表显示什么?两者都打算以UTC显示当前时间吗?或者其中一个应该用UTC显示时间,另一个用于显示火星上某个点的时间?只要它们同步,第二种情况就不会成为问题,对吗?

这正是这里发生的事情。您可以比较不同的内存测量值:

  • 最大居住地
  • 已分配内存总量

最大驻留时间是程序在给定时间内使用的最大内存量。那是19MB。但是,分配内存的总量要多得多,因为这就是GHC的工作原理:它为垃圾收集的对象“分配”内存,这几乎是所有未解包的内容。

让我们检查一个C示例:

int main() {
   int i;
   char * mem;

   for(i = 0; i < 5; ++i) {
      mem = malloc(19 * 1000 * 1000);
      free(mem);
   }
   return 0;
}

每当我们使用malloc时,我们将分配19兆字节的内存。但是,我们之后立即释放内存。因此,我们在一个点上拥有的最大内存量是19兆字节(对于堆栈和程序本身来说,还有更多的内存)。

但是,我们总共分配5 * 19M,总共95M 。不过,我们可以运行我们的小程序,只需20兆美元的RAM。这是总分配内存最大驻留时间之间的差异。请注意,按时间报告的驻留时间始终至少为du <executable>,因为它也必须驻留在内存中。

话虽这么说,生成统计信息的最简单方法是-s,它将显示从Haskell程序的角度来看最大驻留时间是多少。在您的情况下,它将是 1.9M ,堆配置文件中的数字(或者由于分析而增加一倍)。是的,Haskell可执行文件往往变得非常大,因为库是静态链接的。

答案 1 :(得分:4)

time -l显示操作系统(显然)看到的进程(驻留,即未交换)大小。这包括Haskell堆的最大大小的两倍(由于GHC的GC工作方式),以及RTS或其他C库分配的任何其他内容,以及可执行文件本身的代码加上它依赖的库在这种情况下,我猜测19M的主要贡献者是你的exectuable的大小。

total alloc是分配给Haskell堆的总量。它根本不是最大堆大小的衡量标准(这是人们通常所说的&#34;我的程序使用多少内存&#34;)。分配非常便宜,Haskell程序的典型分配率约为1GB / s。

hp2ps输出标题中的数字&#34; 114,272字节x秒&#34;是一个完全不同的东西:它是图形的积分,以字节*秒为单位,而不是以字节为单位。例如,如果您的程序保持10 MB结构4秒钟,那么这将导致此数字增加40 MB * s。

图中显示的大约1.8 MB的数字是Haskell堆的实际最大大小,这可能是您最感兴趣的数字。

您已经省略了有关您的程序执行的最有用的数字来源,它使用+RTS -s运行它(这甚至不需要它使用性能分析构建)。