Heap management

Giorgos Keramidas keramida at ceid.upatras.gr
Thu Oct 26 15:57:24 EEST 2006


On 2006-10-26 11:49, Apostolos Manolitzas <apostolix at gmail.com> wrote:
> Καλησπέρα σε όλους,
> παρατηρώ το εξής φαινόμενο σε ένα σύστημα που τρέχει kernel
> 2.4.12 Έχω μια σερβερ εφαρμογή που δέχεται commands από sockets
> οπότε με τα commands αυτά γίνονται αρκετά new() της τάξης των
> 10^6 αλλά με μεγέθη των Objects αρκετά μικρά 10-100 bytes.
>
> Στην συνέχεια έρχονται οι αντίστοιχες εντολές για την
> απομάκρυνση αυτών των objects με τα αντίστοιχα delete. Έχω
> μετρήσει το πλήθος των new και ειναι το ίδιο με αυτό των delete
> δηλαδή ξέρω σίγουρα οτι δεν έχω Leak, ή αν έχω δεν το βρίσκω ή
> είναι πολύ μικρό.
>
> Το πρόβλημα μου είναι το εξής στην αρχή το πρόγραμμα έχει
> μέγεθος 3Μ όταν κάνω όλα τα new το μέγεθος εκτοξεύεται στα 27Μ
> που το θεωρώ φυσιολογικό γιατί μετράω όλα τα new και είναι πάρα
> πολλά. Το μεγάλο μου ερώτημα είναι γιατί όταν κάνω τα
> αντίστοιχα delete δεν πέφτει πάλι στα 3Μ! ή έστω εκεί κοντά.
> Δηλαδή η μνήμη διατηρείται στα 25Μ και όταν εφαρμόσω πάλι την
> ίδια ακολουθία εντολών (new - delete) η μνήμη ανεβαίνει στα
> 30Μ.

Οι memory allocators δεν επιστρέφουν πάντα τη μνήμη που ελευθερώνεις με
free() ή delete() στο σύστημα.  Αυτό μπορεί να γίνει για διάφορους
λόγους:

    * Επειδή είναι έτσι σχεδιασμένοι.

      Το να ζητήσεις να γίνουν `map' περισσότερες σελίδες μνήμης από το
      σύστημα μπορεί να απαιτεί περισσότερο χρόνο από το να πάρεις μια
      σελίδα που πριν λίγο έκανες free(), η οποία απλά μαρκαρίστηκε ως
      `free' από τον allocator, αλλά παρέμεινε mapped στο virtual space
      του προγράμματος.

    * Επειδή υπάρχει memory fragmentation.

      Ανάλογα με τη σειρά, το χρόνο ζωής και το μέγεθος των αιτήσεων για
      μνήμη, για ΟΠΟΙΟΔΗΠΟΤΕ allocator μπορεί να δημιουργηθεί ένα
      "παθολογικό" pattern στον mapped χώρο μνήμης, που τον αναγκάζει να
      λειτουργεί με μη-βέλτιστο τρόπο.

> Διάβασα για πιθανόν caching που κάνει η μνήμη, οπότε φτιάχνω
> ένα προγραμματάκι που κάνει malloc όση ελεύθερη έχει απομείνει
> ώστε να το στρεσάρω όσο μπορώ, μπας και την δώσει την άτιμη. Το
> αποτέλεσμα είναι οτι δεν την δίνει αυτή την μνήμη.

Οι mapped σελίδες ενός προγράμματος δε μπορούν να επηρεάσουν τις mapped
σελίδες του άλλου, εκτός κι αν φτάσεις σε καταστάσεις που εξαντλείται
όλο το virtual address space, οπότε μάλλον θα ξυπνήσει και ο OOM killer
(out-of-memory killer) του πυρήνα, με όχι τόσο ευχάριστα αποτελέσματα.

> Καλημέρα,
> επανέρχομαι σε αυτό το θέμα γιατί δυστυχώς ακόμα υπάρχει και
> δεν έχω βγάλει άκρη.
>
> Απλά τώρα έχω επιβεβαιώσει οτι δεν υπάρχει καποιο Leak, έχω
> χρησιμοποιήσει valgrind και όλα τα δείχνει καλά.
>
> Επίσης σας δείχνω κάποια στοιχεία σχετικά με το memusage():
>
> Arena 0:
> system bytes     =   26694288
> in use bytes     =    1351624
> Arena 1:
> system bytes     =   25829376
> in use bytes     =      15072
> Total (incl. mmap):
> system bytes     =   52794000
> in use bytes     =    1637032
> max mmap regions =          2
> max mmap bytes   =     270336
>  mem_usage_stats():
> arena : 26694288
> ordblks : 1657
> smbblks : 97
> hblks : 2
> hblkhd : 270336
> uordblks : 1351624
> fordblks : 25342664
> keepcost : 24024
>
> Οπότε το πρόβλημα είναι γιατί δεν επιστρέφει την μνήμη, και μερικές
> φορές οι μνήμη ανεβαίνει αντί να διατηρείται. Επίσης σε μετρήσεις που
> έκανα σχετικά με τα μεγέθη των objects που γίνονται allocate έχω το
> εξής συμπέρασμα:
>
> in use bytes 2119740 in use count 35903 mallocs 19889373 frees 19853470
> fragments 12008 loops 391985952 loops/malloc 19 heap len 1 errors 0
> allocations per size
>       size       count
>          1          50
>          2       16834
>          4       10144
>          8     4064548
>         16     5562686
>         32     6306117
>         64     2598808
>        128      932319
>        256      277678
>        512       94975
>       1024          17
>       2048       25177
>       4096           6
>       8192           6
>      16384           3
>      32768           2
>     131072           1
>     262144           2
>
> Έχει κανείς καμιά ιδέα σχετικά με το θέμα; Έπαιξα και με το
> mallopt αλλά δεν είχα κάποια τύχη.

Γενικά, το μεγαλύτερο ποσοστό από τα allocations που κάνεις είναι
σε μεγέθη 8-256 bytes, με τη συντριπτική πλειοψηφία από αυτά σε
μεγέθη από 8-64 bytes:

,----------------------------------------------------------------
| > sed -e 's/^/% /' malloc.dat
| %       1          50
| %       2       16834
| %       4       10144
| %       8     4064548
| %      16     5562686
| %      32     6306117
| %      64     2598808
| %     128      932319
| %     256      277678
| %     512       94975
| %    1024          17
| %    2048       25177
| %    4096           6
| %    8192           6
| %   16384           3
| %   32768           2
| %  131072           1
| %  262144           2
| > perl pctgraph.pl < malloc.dat
|               1              50   0.00%
|               2           16834   0.08%
|               4           10144   0.05%
|               8         4064548  20.44% ############
|              16         5562686  27.97% ################
|              32         6306117  31.71% ###################
|              64         2598808  13.07% #######
|             128          932319   4.69% ##
|             256          277678   1.40%
|             512           94975   0.48%
|            1024              17   0.00%
|            2048           25177   0.13%
|            4096               6   0.00%
|            8192               6   0.00%
|           16384               3   0.00%
|           32768               2   0.00%
|          131072               1   0.00%
|          262144               2   0.00%
| >
`----------------------------------------------------------------

Αυτό ίσως να μπορεί να 'διορθωθεί' κάπως, με preallocation ενός pool από
δομές που χρησιμοποιείς συχνά και εσωτερική χρήση αυτού του pool.  Θα
πρέπει πρώτα όμως να βρεις ποιές είναι οι δομές που έχουν μέγεθος από
8-32 bytes που χρησιμοποιούνται τόσο συχνά.

Προσοχή όμως.  Δεν είμαι σίγουρος ότι απλά χρησιμοποιώντας ένα pool από
preallocated δομές με τον σωστό τύπο, θα κερδίσεις απαραίτητα σε όλα τα
πράγματα.  Τότε μπορεί να ανακαλύψεις ότι βελτιστοποιώντας το πρόγραμμα
ως προς τη χρήση του μεγέθους της μνήμης, χάνει πολύ σε ταχύτητα
(επειδή, π.χ. ο memory allocator "θυσιάζει" το μέγεθος της mapped
μνήμης, με απώτερο σκοπό να αποφύγει φαινόμενα `cache-line pollution',
που επιβραδύνουν το πρόγραμμα τελικά).




More information about the Linux-greek-users mailing list