2016-10-14 17 views
5

Tôi đang cố gắng làm cho tinh thần từ hồ sơ của GHC. Có một ứng dụng khá đơn giản, sử dụng các thư viện werqlens-aeson và trong khi tìm hiểu về hồ sơ GHC, tôi quyết định chơi với nó một chút.Có ý nghĩa từ hồ sơ GHC

Sử dụng các tùy chọn khác nhau (time công cụ, +RTS -p -RTS+RTS -p -h) Tôi đã nhận được số lượng sử dụng bộ nhớ khác nhau hoàn toàn. Có tất cả những con số đó, bây giờ tôi hoàn toàn mất việc cố gắng để hiểu những gì đang xảy ra, và bao nhiêu bộ nhớ ứng dụng thực sự sử dụng.

Tình huống này nhắc tôi cụm từ của Arthur Bloch: "Một người đàn ông với đồng hồ biết thời gian là gì. Một người đàn ông với hai chiếc đồng hồ không bao giờ chắc chắn."

Bạn có thể, hãy gợi ý cho tôi, cách tôi có thể đọc tất cả những con số đó và ý nghĩa của từng con số là gì.

Dưới đây là những con số:

time -l báo cáo xung quanh 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 

Sử dụng +RTS -p -RTS báo cáo cờ xung quanh 92M. Mặc dù nó nói "tổng alloc" có vẻ như xa lạ với tôi, đó là một ứng dụng đơn giản như thế này có thể phân bổ và giải phóng 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 cho tôi xem hình ảnh sau và hai con số: 114K trong tiêu đề và một cái gì đó xung quanh 1.8Mb trên biểu đồ. Memory Profiling

Và, chỉ trong trường hợp, đây là ứng dụng:

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 

UPDATE 1: Cảm ơn tất cả mọi người để được trả lời tốt đáng kinh ngạc. Như đã được đề xuất, tôi thêm đầu ra +RTS -s, vì vậy toàn bộ hình ảnh sẽ được xây dựng cho tất cả những ai đọc nó.

#./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 

UPDATE 2: Kích thước của thực thi:

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

Trả lời

7

Một người đàn ông đeo đồng hồ biết thời gian là bao giờ. Một người đàn ông đeo hai cái đồng hồ là không chắc chắn.

Ah, nhưng hai đồng hồ hiển thị những gì? Cả hai có nghĩa là hiển thị thời gian hiện tại trong UTC không? Hoặc là một trong số họ phải cho thấy thời gian ở UTC, và một trong những thời gian trên một điểm nhất định trên sao Hỏa? Miễn là chúng được đồng bộ, kịch bản thứ hai sẽ không thành vấn đề, phải không?

Và đó chính xác là những gì đang xảy ra ở đây. Bạn so sánh đo bộ nhớ khác nhau:

  • các cư trú tối đa
  • các tổng dung lượng bộ nhớ được phân bổ

Các nội trú tối đa là số tiền cao nhất của bộ nhớ chương trình của bạn bao giờ sử dụng tại một định thời gian. Đó là 19MB. Tuy nhiên, tổng số lượng bộ nhớ được phân bổ là rất nhiều, vì đó là cách GHC hoạt động: nó "cấp phát" bộ nhớ cho các đối tượng được thu thập rác, gần như mọi thứ không được giải nén.

Chúng ta hãy kiểm tra một ví dụ C cho việc này:

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

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

Bất cứ khi nào chúng tôi sử dụng malloc, chúng tôi sẽ phân bổ 19 MB bộ nhớ. Tuy nhiên, chúng tôi giải phóng bộ nhớ ngay sau đó. Số lượng bộ nhớ cao nhất mà chúng tôi từng có tại một thời điểm là 19 MB (và nhiều hơn một chút đối với ngăn xếp và chính chương trình).

Tuy nhiên, tổng cộng, chúng tôi phân bổ 5 * 19M, 95M tổng số. Tuy nhiên, chúng tôi có thể chạy chương trình nhỏ của chúng tôi chỉ với 20 megabyte RAM. Đó là sự khác biệt giữa tổng số bộ nhớ được phân bổ số lượng tối đa là. Lưu ý rằng thời gian cư trú được báo cáo theo thời gian luôn là ít nhất du <executable>, do đó cũng phải nằm trong bộ nhớ.

Điều đó đang được nói, cách dễ nhất để tạo thống kê là -s, sẽ cho biết mức độ lưu trú tối đa theo quan điểm chương trình của Haskell. Trong trường hợp của bạn, nó sẽ là 1.9M, số trong hồ sơ heap của bạn (hoặc tăng gấp đôi số tiền do hồ sơ). Và vâng, các tệp thực thi Haskell có xu hướng cực kỳ lớn, vì các thư viện được liên kết tĩnh.

+0

Cảm ơn Zeta cho thư trả lời của bạn. Tôi có một câu hỏi nữa. Kích thước của nhị phân là 63M.Nó có nghĩa là nhị phân không được nạp đầy đủ vào bộ nhớ bằng cách nào đó? – Slabko

+0

@Slabko: Tôi không có môi trường Unix để kiểm tra ngữ nghĩa của 'time -l' tại thời điểm này, do đó có thể không chính xác 100%. Tuy nhiên, tôi đã không tìm thấy bất kỳ trang man nào mà '-l' được mô tả. Bạn sử dụng 'time' nào? – Zeta

+0

Tôi sử dụng Mac OS 10.11 và phiên bản được nhúng của 'time'. Nó sử dụng 'getrusage' của POSIX. Điều thú vị là, kích thước trả về 'getusage' trong kb và 19070976 kb là một con số thực sự lớn. Tôi đã thử GNU 'time' trên Linux; với kích thước nhị phân là 48M, nó vẫn cho rằng kích thước thiết lập tối đa là 32820Kb. Nhưng tôi nghĩ, tôi đã nhận được nhiều câu trả lời tốt cho câu hỏi của mình, và bây giờ tôi hiểu cách tôi nên đọc số hồ sơ GHC. Tôi không nghĩ rằng cách 'thời gian' hoạt động có liên quan đến sự hiểu biết về tối ưu hóa bộ nhớ Haskell trong trường hợp của tôi. Cảm ơn bạn rất nhiều vì sự giúp đỡ của bạn, tôi thực sự đánh giá cao nó! – Slabko

4

time -l được hiển thị (thường trú, tức là không hoán đổi) kích thước của quá trình này như được thấy bởi hệ điều hành (rõ ràng) . Điều này bao gồm gấp đôi kích thước tối đa của Haskell heap (do cách mà GC của GHC hoạt động), cộng với bất cứ thứ gì khác được RTS hoặc thư viện C phân bổ, cộng với mã thực thi của bạn cùng với các thư viện phụ thuộc vào, v.v. 'm đoán trong trường hợp này đóng góp chính cho 19M là kích thước của bạn vô giá trị.

total alloc là tổng số tiền được phân bổ trên heapell Heapell. Nó không phải là ở tất cả các biện pháp kích thước heap tối đa (đó là những gì mọi người thường có nghĩa là "bao nhiêu bộ nhớ là chương trình của tôi bằng cách sử dụng"). Phân bổ là rất rẻ và tỷ lệ phân bổ khoảng 1GB/s là điển hình cho một chương trình Haskell.

Số trong tiêu đề của đầu ra hp2ps "114,272 bytes x giây" là một cái gì đó hoàn toàn khác một lần nữa: đó là tích phân của biểu đồ và được tính theo byte * giây, không tính theo byte. Ví dụ: nếu chương trình của bạn giữ cấu trúc 10 MB trong 4 giây thì điều đó sẽ làm cho con số này tăng thêm 40 MB * s.

Số khoảng 1,8 MB thể hiện trong đồ thị dưới là kích thước tối đa thực tế của đống Haskell, mà có lẽ là số bạn đang quan tâm nhất.

Bạn đã bỏ qua các nguồn hữu ích nhất của số về thực thi chương trình của bạn, đang chạy nó với +RTS -s (điều này thậm chí không yêu cầu nó được xây dựng bằng lược tả).

Các vấn đề liên quan