Pracuję nad dużym projektem C ++. Składa się z serwera, który udostępnia interfejs API REST, zapewniając prosty i przyjazny interfejs dla bardzo szerokiego systemu zawierającego wiele innych serwerów. Baza kodów jest dość duża i złożona i ewoluowała w czasie bez odpowiedniego projektu z góry. Moim zadaniem jest wdrażanie nowych funkcji i refaktoryzacja / naprawa starego kodu, aby uczynić go bardziej stabilnym i niezawodnym.
W tej chwili serwer tworzy wiele długo żyjących obiektów, które nigdy nie są kończone ani usuwane po zakończeniu procesu. To sprawia, że Valgrind jest prawie bezużyteczny do wykrywania wycieków, ponieważ nie można odróżnić tysięcy (wątpliwie) uzasadnionych wycieków od „niebezpiecznych”.
Moim pomysłem jest upewnienie się, że wszystkie obiekty zostaną usunięte przed zakończeniem, ale kiedy złożyłem tę propozycję, moi koledzy i mój szef sprzeciwili się mi, wskazując, że system operacyjny i tak zwolni tę pamięć (co jest oczywiste dla wszystkich) i usuwając obiekty spowolni zamykanie serwera (co w tej chwili jest w zasadzie wezwaniem do std::exit
). Odpowiedziałem, że „czysta” procedura zamykania niekoniecznie oznacza, że trzeba z niej skorzystać. Zawsze możemy zadzwonić std::quick_exit
lub po prostu kill -9
proces, jeśli czujemy się niecierpliwi.
Odpowiedzieli: „większość demonów i procesów Linuksa nie zawraca sobie głowy zwalnianiem pamięci podczas zamykania”. Chociaż widzę to, prawdą jest również to, że nasz projekt potrzebuje dokładnego debugowania pamięci, ponieważ już znalazłem uszkodzenie pamięci, podwójne zwolnienia i niezainicjowane zmienne.
Jakie są Twoje myśli? Czy dążę do bezcelowego przedsięwzięcia? Jeśli nie, jak mogę przekonać moich kolegów i szefa? Jeśli tak, dlaczego i co powinienem zamiast tego zrobić?
Odpowiedzi:
Dodaj przełącznik do procesu serwera, którego można użyć podczas pomiarów valgrind, które zwolnią całą pamięć. Możesz użyć tego przełącznika do testowania. Wpływ będzie minimalny podczas normalnych operacji.
Mieliśmy długotrwały proces, który zająłby kilka minut, aby uwolnić 1000 obiektów. O wiele skuteczniej było po prostu wyjść i pozwolić im umrzeć. Niestety, jak wskazano, utrudniło to wykrycie prawdziwych wycieków pamięci za pomocą valgrind lub innych narzędzi.
Był to dobry kompromis dla naszych testów, nie wpływając na normalną wydajność.
źródło
Kluczem jest tutaj:
To prawie bezpośrednio sugeruje, że twoja baza kodu jest poskładana z niczego poza nadzieją i sznurkiem. Kompetentni programiści C ++ nie mają podwójnych zwolnień.
Absolutnie dążysz do bezsensownego przedsięwzięcia - w tym przypadku rozwiązujesz jeden drobny objaw rzeczywistego problemu, którym jest to, że Twój kod jest mniej więcej tak niezawodny, jak moduł serwisowy Apollo 13.
Jeśli poprawnie zaprogramujesz swój serwer za pomocą RAII, problemy te nie wystąpią, a problem w twoim pytaniu zostanie wyeliminowany. Ponadto Twój kod może od czasu do czasu działać poprawnie. Jest to zatem zdecydowanie najlepsza opcja.
źródło
Jednym dobrym podejściem byłoby zawężenie dyskusji z kolegami za pomocą klasyfikacji. Biorąc pod uwagę dużą bazę kodu, z pewnością nie ma jednego pojedynczego powodu, ale raczej wiele (możliwych do zidentyfikowania) powodów dla długo żyjących obiektów.
Przykłady:
Przedmioty długo żyjące, do których nikt się nie odwołuje (prawdziwe wycieki). Jest to błąd logiki programowania. Napraw te o niższym priorytecie, JEŚLI są odpowiedzialne za to, że wraz z upływem czasu zwiększysz pamięć (i pogorszysz jakość aplikacji). Jeśli sprawią, że ślad pamięci wzrośnie z czasem, napraw je z wyższym priorytetem.
Długowieczne obiekty, do których wciąż się odwołujemy, ale nie są już używane (ze względu na logikę programu), ale które nie powodują powiększenia pamięci. Przejrzyj kod i spróbuj znaleźć inne błędy, które do tego prowadzą. Dodaj komentarze do bazy kodu, jeśli jest to celowa optymalizacja (wydajności).
Przedmioty długowieczne „według projektu”. Na przykład wzór singletonu. Naprawdę trudno się ich pozbyć, zwłaszcza jeśli jest to aplikacja wielowątkowa.
Przedmioty z recyklingu. Przedmioty długo żyjące nie zawsze muszą być złe. Mogą być również korzystne. Zamiast alokacji / dezalokacji pamięci o wysokiej częstotliwości, dodawanie aktualnie nieużywanych obiektów do kontenera w celu pobrania, gdy taki blok pamięci jest ponownie potrzebny, może przyspieszyć aplikację i uniknąć fragmentacji sterty. Powinny być łatwe do zwolnienia w momencie wyłączenia, być może w specjalnej wersji „oprzyrządowanie / sprawdzone”.
„współdzielone obiekty” - obiekty, które są używane (przywoływane) przez wiele innych obiektów i nikt nie wie dokładnie, kiedy można je zapisać, aby je zwolnić. Zastanów się, czy nie zamienić ich w obiekty zliczone według odniesień.
Po sklasyfikowaniu prawdziwych przyczyn tych niesprawdzonych obiektów, o wiele łatwiej jest wprowadzić przypadek po dyskusji i znaleźć rozwiązanie.
źródło
IMHO, czasy życia tych obiektów nigdy nie powinny być po prostu stworzone i pozostawione na śmierć, gdy system się wyłączy. Cuchną zmiennymi globalnymi, o których wszyscy wiemy, że są złe złe złe złe. Zwłaszcza w dobie inteligentnych wskazówek nie ma innego powodu niż lenistwo. Ale, co ważniejsze, zwiększa poziom długu technicznego w twoim systemie, z którym ktoś może kiedyś sobie poradzić.
Idea „długu technicznego” polega na tym, że kiedy używasz takiego skrótu, gdy ktoś chce zmienić kod w przyszłości (powiedzmy, że chcę mieć możliwość przejścia klienta w „tryb offline” lub „tryb uśpienia” ”lub chcę móc przełączać serwery bez ponownego uruchamiania procesu), będą musieli włożyć wysiłek, aby zrobić to, co pomijasz. Ale będą utrzymywać twój kod, więc nie będą wiedzieć o nim prawie tak dużo jak ty, więc zajmie im to dużo dłużej (nie mówię 20% dłużej, mówię 20 razy dłużej!). Nawet jeśli to ty, to nie będziesz pracował nad tym konkretnym kodem przez tygodnie lub miesiące, a jego prawidłowe wdrożenie zajmie znacznie więcej czasu.
W tym przypadku wygląda na to, że masz bardzo ścisłe połączenie między obiektem serwera a obiektami „długowiecznymi” ... są chwile, w których rekord mógłby (i powinien) przeżyć połączenie z serwerem. Utrzymanie każdej pojedynczej zmiany obiektu na serwerze może być nadmiernie drogie, dlatego zazwyczaj lepiej jest, aby spawnowane obiekty naprawdę były uchwytami podręcznymi dla obiektu serwera, z wywołaniami zapisu i aktualizacji, które faktycznie zmieniają serwer. Jest to powszechnie nazywane wzorem aktywnego zapisu. Widzieć:
http://en.wikipedia.org/wiki/Active_record_pattern
W C ++ chciałbym, aby każdy aktywny rekord miał słabą wartość na serwerze, co mogłoby inteligentnie wyrzucić rzeczy, jeśli połączenie z serwerem ulegnie awarii. Klasy te mogą być wypełnione leniwie lub wsadowo, w zależności od potrzeb, ale żywotność tych obiektów powinna być tylko tam, gdzie są używane.
Zobacz też:
Czy marnowanie zasobów jest stratą czasu przed zakończeniem procesu?
Inny
źródło
This reeks of global variables
Jak przechodzisz od „istnieją tysiące obiektów, które trzeba uwolnić” do „muszą być globalne”? To niezły skok logiki.Jeśli potrafisz łatwo zidentyfikować, gdzie przydzielone są obiekty, które powinny przetrwać w nieskończoność, raz można byłoby je przydzielić za pomocą alternatywnego mechanizmu alokacji, tak aby albo nie pojawiały się w raporcie wycieku valgrind, albo po prostu wyglądały jak pojedynczy przydział.
Jeśli nie znasz tego pomysłu, oto artykuł na temat impotentnego przydzielania pamięci niestandardowej w c ++ , chociaż zauważ, że twoje rozwiązanie może być prostsze niż przykłady w tym artykule, ponieważ nie musisz w ogóle zajmować się usuwaniem !
źródło