Wycieki pamięci przy TStringList.LoadFromFile

0

Mam programik, który dość często wczytuje dane z pliku. Po wczytaniu danych z pliku (st.LoadFromFile) w menadżerze zadań obciążenia pamięci zwiększa mi się o jakieś 60MB. Natomiast po zwolnieniu zmiennej st (st.Free) odzyskuję tylko 50MB. I tak przy każdym wykonaniu poniższego kodu gdzieś gubię 10MB pamięci. Co robię źle?
Korzystam z Lazarusa 2.0.12

Pozdrawiam Sc0li0sis

var st: TStringList;
...

try
   st:= TStringList.Create();
   st.LoadFromFile(ff);  

   // tu wykonywane są różne operacje, które nie wpływają na obciążenie pamięci
finally
   st.free;
end;            

1

Użyj memchecka

8

Po pierwsze, nie twórz obiektów wewnątrz try, a przed nim — jeśli kostruktor z jakiegoś powodu rzuci wyjątek, to blok finally wywali kolejny, bo wykona Free na nieprawidłowej referencji. W przypadku TStringList nie ma to większego znaczenia, ale konstruktor np. TFileStream może rzucić wyjątkiem jeśli nie będzie pliku lub nie będzie miał do niego dostępu (wedle wskazanych flag).

Poprawna konstrukcja wygląda jak niżej, dla dowolnego typu tworzonej klasy:

Variable := TSomeClass.Create();
try
  // do something
finally
  Variable.Free();
end;

Po drugie, metoda TStringList.LoadFromFile nie posiada wycieków pamięci, a sam menedżer zadań nie jest dobrym narzędziem do szukania problemów z pamięcią. Dobrym narzędziem za to jest moduł HeapTrc, znajdujący się w bibliotece standardowej. Możesz go samodzielnie dopisać do listy uses w głównym module projektu, a możesz też zaznaczyć jego użycie w oknie ustawień projektu.

Jeśli robisz program okienkowy, to istnienie modułu HeapTrc w sekcji uses spowoduje, że po zamknięciu programu zostanie wyświetlone okienko z podsumowaniem użycia pamięci. Jeśli program generuje wycieki, to po zamknięciu programu wyskoczy wiele okien, jedno po drugim i każde z osobna będzie pokazywać jakiś fragment logu. Dlatego lepszym sposobem jest uruchomienie programu z poziomu konsoli — wtedy cały log zostanie wyświetlony w konsoli.

Jest to o tyle eleganckie rozwiązanie, że w logu są szczegółowe informacje na temat tego ile pamięci wyciekło, a także w którym module i w której linijce jest alokowana pamięć, która później nigdy nie jest zwalniana. W ten sposób bardzo szybko można namierzyć alokacje do późniejszego zwolnienia.

Natomiast menedżer zadań może pokazywać większe zużycie pamięci po zwolnieniu listy niż przed jej utworzeniem, bo menedżer pamieci może zostawić pewne bloki pamięci ”na później”. Nie powinieneś się przejmować tym co robi menedżer pamięci, bo i tak nie masz na niego wpływu. Jeśli log generowany przez HeapTrc nie pokazuje wycieków, to reszta nie ma znaczenia.

0

Wielkie dzięki za szybką odpowiedź.

Po użyciu HeapTrc mam taki komunikat:

screenshot-20220706113518.png

Więc chyba wszystko jest OK. Ale, po wielokrotnym użyciu

try
   st:= TStringList.Create();
   st.LoadFromFile(ff);  

   // tu wykonywane są różne operacje, które nie wpływają na obciążenie pamięci
finally
   st.free;
end;            

Pojawia się błąd OutOfMemory

screenshot-20220706113727.png

Więc coś chyba jest nie tak.

Program po zamknięciu zwalnia całą pamięć, ale w trakcie działania po każdym kolejnym LoadFromFile i Free coś pozostaje w pamięci. I po wielokrotnym użyciu wreszcie wywala OutOfMemory.

Nie mam pojęcia co robię źle :(

Pozdrawiam
Sc0li0sis

4

Spróbuj dodać st.Clear przed finally a w ogóle sprawdziłeś czy tylko "tu wykonywane są różne operacje, które nie wpływają na obciążenie pamięci" na pewno nie ma wpływu czy tylko tak Ci się wydaje? Spróbuj wykomentować ten blok. Poza tym st:= TStringList.Create(); przenieś przed try inaczej jeżeli tworzenie TStringList się nie powiedzie program st.Free będzie chciało się wykońać co spowoduje błąd.

5
kAzek napisał(a):

Spróbuj dodać st.Clear przed finally a w ogóle sprawdziłeś czy tylko "tu wykonywane są różne operacje, które nie wpływają na obciążenie pamięci" na pewno nie ma wpływu czy tylko tak Ci się wydaje?

Chyba mu się wydaje... 😉

Spróbuj wykomentować ten blok. Poza tym st:= TStringList.Create(); przenieś przed try inaczej jeżeli tworzenie TStringList się nie powiedzie program st.Free będzie chciało się wykońać co spowoduje błąd.

To już tam w sumie bez znaczenia, ale mam propozycję dla OP; skoro "tu wykonywane są różne operacje, które nie wpływają na obciążenie pamięci" to skompiluj i uruchom wielokrotnie taki kod:

var 
  st: TStringList;
begin
  st:= TStringList.Create();
try
   st.LoadFromFile(ff);  
finally
   st.free;
end;           

I jak będziesz dalej miał wycieki to męcz FPC.
Ale jeśli nie, to nie zawracaj głowy i szukaj błędu u siebie

2
Sc0li0sis napisał(a):

Po użyciu HeapTrc mam taki komunikat:

screenshot-20220706113518.png

No to dobry komunikat, bo nie ma żadnych wycieków. Niech Cię nie zwiedzie tytuł okna — ono zawsze będzie miało tytuł Error, nawet jeśli nie ma żadnych wycieków pamięci. Nie wiem dlaczego tak jest, nie sprawdzałem tego nigdy, ale taki tytuł to norma. W każdym razie interesuj się trzecią linijką:

0 unfreed memory blocks : 0

Dwie pierwsze linijki traktuj raczej jako luźną informację o tym, jak dużo bloków zostało zaalokowanych w czasie całej sesji programu. W przypadku wycieków pamięci, pod powyższym podsumowaniem pojawią się konkretne informacje o wyciekach — liczba niezwolnionych bloków i ich adresy w pamięci, ścieżki alokacji, nazwy modułów i numery linii.

Program po zamknięciu zwalnia całą pamięć, ale w trakcie działania po każdym kolejnym LoadFromFile i Free coś pozostaje w pamięci.

Co to znaczy, że „coś zostaje”? Czy chodzi o to, że HeapTrc pokazuje wycieki w logu po zamknięciu programu, czy znów sugerujesz się menedżerem zadań?

I po wielokrotnym użyciu wreszcie wywala OutOfMemory.

Wszystko zależy od tego co dokładnie powoduje wycieki i niestety wszystko wskazuje na to, że błąd leży po Twojej stronie. Albo kod operujący na liście alokuje pamięć bez opamiętania, albo załadowany do listy plik jest po prostu za duży (choć musiałby ważyć gigabajty, aby faktycznie brakło pamięci). Bez konkretów nic więcej nie możemy doradzić — co najwyżej tyle, aby zaremować kod pod LoadFromFile i w ten sposób testować klasę TStringList.

1

// tu wykonywane są różne operacje, które nie wpływają na obciążenie pamięci

jak napisali @furious programming , @wloochacz, @kAzek , clou Twojego problemu może się kryć w kodzie który wg Ciebie dla tego problemu jest bez znaczenia i którego nie pokazałeś

0

Witajcie, mieliście racje, po przejrzeniu kodu okazało się, że moim kodzie jest błąd.
Dzięki za wsparcie i szybkie odpowiedzi
Sc0li0sis

1 użytkowników online, w tym zalogowanych: 0, gości: 1