Czy istnieje przeciek pamięci?

class myclass : public boost::enable_shared_from_this<myclass>{
//...
    void broadcast(const char *buf){
    broadcast(new std::string(buf));
    }

    void broadcast(std::string *buf){
    boost::shared_ptr<std::string> msg(buf);
    }
//...
};  

(To jest uproszczona wersja, w której nadal występuje problem - normalnie wykonuję prawdziwą pracę w tym drugim wywołaniu broadcast!) Założyłem, że pierwsze połączenie dostaje trochę pamięci, a ponieważ nic nie robię z inteligentnym wskaźnikiem, drugie połączenie natychmiast je usunie. Prosty? Ale kiedy uruchamiam mój program, pamięć rośnie z czasem, skokowo. Jednak kiedy komentuję jedyne wywołanie w programie do broadcast(), tak nie jest!

Wyjście ps dla wersji bez broadcast():

 %CPU  %MEM     VSZ    RSS  TIME
 3.2   0.0     158068  1988 0:00 
 3.3   0.0     158068  1988 0:25  (12 mins later)

Z wywołaniem broadcast() (na Ubuntu 10.04, g++ 4.4, boost 1.40)

 %CPU  %MEM     VSZ    RSS  TIME
 1.0    0.0    158068  1980 0:00
 3.3    0.0    158068  1988 0:04  (2 mins)
 3.4    0.0    223604  1996 0:06  (3.5 mins)
 3.3    0.0    223604  2000 0:09
 3.1    0.0    223604  2000 2:21  (82 mins)
 3.1    0.0    223604  2000 3:50  (120 mins)

(Widzenie tego skoku po około 3 minutach jest powtarzalne w kilku próbach, które do tej pory próbowałem).

Z wezwaniem do broadcast() (na Centos 5.6, g++ 4.1, boost 1.41)

 %CPU  %MEM     VSZ    RSS  TIME
 0.0    0.0     51224  1744 0:00
 0.0    0.0     51224  1744 0:00  (30s)
 1.1    0.0    182296  1776 0:02  (3.5 mins)
 0.7    0.0    182296  1776 0:03
 0.7    0.0    182296  1776 0:09  (20 mins)
 0.7    0.0    247832  1788 0:14  (34 mins)
 0.7    0.0    247832  1788 0:17
 0.7    0.0    247832  1788 0:24  (55 mins)
 0.7    0.0    247832  1788 0:36  (71 mins)

Oto jak wywoływany jest broadcast() (z boost::asio timer) i teraz zastanawiam się, czy to może mieć znaczenie:

void callback(){
//...
timer.expires_from_now(boost::posix_time::milliseconds(20));
//...
char buf[512];
sprintf(buf,"...");
broadcast(buf);
timer.async_wait(boost::bind(&myclass::callback, shared_from_this() ));
//...
}

(callback jest w tej samej klasie co funkcja broadcast)

Mam 4 z tych liczników, a mój io_service.run() jest wywoływany przez pulę 3 wątków. Mój limit czasu 20 ms oznacza, że ​​każdy timer wywołuje broadcast() 50 razy na sekundę. Ustawiam wygaśnięcie na początku mojej funkcji i uruchamiam timer pod koniec. Wyeliminowany kod nie robi tak wiele; wysyłanie informacji debugowania do std::cout jest prawdopodobnie zadaniem najbardziej obciążającym procesor. Przypuszczam, że czasami może się zdarzyć, że timer uruchamia się natychmiast; ale nadal nie widzę, jaki byłby to problem, nie mówiąc już o spowodowaniu wycieku pamięci.

(Nawiasem mówiąc, program działa dobrze, nawet podczas wykonywania pełnych zadań; nabrałem podejrzeń tylko wtedy, gdy zauważyłem, że zużycie pamięci zgłoszone przez ps wzrosło.)

AKTUALIZACJA: Dziękujemy za odpowiedzi i komentarze. Dodam, że pozostawiłem program działający na każdym systemie na kolejne kilka godzin i zużycie pamięci już nie wzrosło. (Byłem też gotów odrzucić to jako jednorazową restrukturyzację stosu lub coś w tym rodzaju, kiedy wersja Centos skoczyła po raz drugi.) W każdym razie dobrze jest wiedzieć, że moje rozumienie inteligentnych wskaźników nadal jest dobre i że jest nie ma dziwnego przypadku narożnego z gwintowaniem, o który muszę się martwić.

1
Darren Cook 22 luty 2012, 10:39

2 odpowiedzi

Najlepsza odpowiedź

W przypadku wycieku przydzielasz std::string (20 bajtów, mniej więcej) 50 razy na sekundę. w ciągu 1 godziny powinieneś zostać przydzielony... 3600*50*20 = 3,4MBytes.

Widzisz, nie ma nic wspólnego z 64K, prawdopodobnie jest to spowodowane sposobem, w jaki system przydziela pamięć procesowi, który new przydziela podrzędnie do zmiennych.

System, gdy coś jest usuwane, musi to „zbierać śmieci”, umieszczając je z powrotem w dostępnym łańcuchu pamięci w celu dalszych alokacji. Ale ponieważ zajmuje to trochę czasu, większość systemów nie wykonuje tej operacji, dopóki uwolniona pamięć nie przekroczy pewnych ilości, aby można było dokonać „przepakowania”.

2
Emilio Garavaglia 22 luty 2012, 11:26

Cóż, to, co się tutaj dzieje, prawdopodobnie nie polega na tym, że twój program przecieka, ale że z jakiegoś powodu alokator pamięci systemowej postanowił zachować kolejną stronę 64 kB dla twojej aplikacji. Gdyby w tym momencie wystąpił stały wyciek pamięci, z częstotliwością 50 Hz, miałoby to znacznie bardziej dramatyczny efekt!

Nie wiem dokładnie, dlaczego tak się dzieje po 3 minutach (nie jestem ekspertem w tej dziedzinie), ale domyślam się, że w grę wchodzą pewne heurystyki i statystyki. Albo może po prostu być pofragmentowana sterta.

Inną rzeczą, która mogła się zdarzyć, jest to, że wiadomości, które trzymasz w buforze, z czasem stają się dłuższe :)

1
Johan Kotlinski 22 luty 2012, 11:10