2009-04-12 8 views
12

Napisałem konwerter, który pobiera pliki xml openstreetmap i konwertuje je do binarnego formatu renderowania środowiska wykonawczego, który zazwyczaj wynosi około 10% pierwotnego rozmiaru. Rozmiary plików wejściowych są zazwyczaj 3 gb i większe. Pliki wejściowe nie są ładowane do pamięci wszystkie na raz, ale przesyłane strumieniowo jako punkty i polys są gromadzone, a następnie uruchamiany jest plik bsp, a plik jest wyprowadzany. Ostatnio na większych plikach kończy się pamięć i umiera (ten ma 14 milionów punktów i 1 milion wielokątów). Zazwyczaj mój program używa około 1 gb do 1,2 gb pamięci RAM, kiedy tak się dzieje. Próbowałem zwiększyć pamięć wirtualną z 2 do 8 GB (na XP), ale ta zmiana nie przyniosła żadnego efektu. Ponadto, ponieważ ten kod jest open-source chciałbym, aby działał niezależnie od dostępnego RAM (choć wolniejszy), działa na Windows, Linux i Mac.Jak uniknąć wyczerpania pamięci w aplikacji wykorzystującej duże ilości pamięci? C/C++

Jakich technik mogę użyć, aby zabrakło w nim pamięci? Przetwarzanie danych w mniejszych podzestawach, a następnie łączenie wyników końcowych? Korzystasz z mojego własnego programu obsługi typu pamięci wirtualnej? Jakieś inne pomysły?

Odpowiedz

14

Po pierwsze, w systemie 32-bitowym zawsze będzie ograniczone do 4 GB pamięci, bez względu na ustawienia pliku stronicowania. (A z tego tylko 2GB będzie dostępne dla twojego procesu w systemie Windows.) W Linuksie będziesz miał zwykle około 3GB dostępnego)

Pierwszym oczywistym rozwiązaniem jest przejście na 64-bitowy system operacyjny i skompilowanie aplikacja dla 64-bitów. Daje to ogromną przestrzeń pamięci wirtualnej do użycia, a system operacyjny wymieni dane z pliku stronicowania, jeśli to konieczne, aby wszystko działało.

Po drugie, przydzielanie mniejszych porcji pamięci na raz może pomóc. Często łatwiej jest znaleźć 4 256 MB porcji wolnej pamięci niż jedna porcja 1 GB.

Po trzecie, podziel się problemem. Nie przetwarzaj całego zestawu danych naraz, ale spróbuj załadować i przetworzyć tylko niewielką sekcję na raz.

+0

System Windows może mieć 3 GB przestrzeni wirtualnej z/LARGEADDRESSAWARE –

+0

Flaga umożliwia procesowi użycie do 4 GB *, jeśli system operacyjny może go dostarczyć *. Zwykle system Windows jest wciąż skonfigurowany tak, aby dawał tylko 2 GB dla każdego procesu. Można to zmienić, ryzykując niestabilność kierowcy, aby dać 3GB, tak. Dzięki PAE możesz uzyskać jeszcze więcej. Ale 64-bit to prawdopodobnie lepszy zakład. – jalf

+1

Imho, trzecia opcja jest najważniejsza. Oprócz kontroli nad pamięcią umożliwia także równoległe przetwarzanie. – xtofl

4

Wygląda na to, że już stosujesz podejście oparte na przetwarzaniu XML w oparciu o SAX (ładowanie XML-a zamiast wszystkich jednocześnie).

Rozwiązaniem jest prawie zawsze zmiana algorytmu, aby zredukować problem na mniejsze części. Fizycznie nie przydzielaj za dużo pamięci za jednym razem, czytaj tylko to, czego potrzebujesz, przetwórz, a następnie zapisz.

Czasami można rozszerzyć pamięć za pomocą dysku twardego, gdy jest to konieczne w danym algorytmie.

Jeśli nie możesz podzielić algorytmu, prawdopodobnie potrzebujesz czegoś takiego jak memory mapped files.

W najgorszym przypadku możesz spróbować użyć czegoś takiego jak VirtualAlloc, jeśli używasz systemu Windows. Jeśli korzystasz z systemu 32-bitowego, możesz spróbować użyć czegoś takiego jak Physical Address Extension (PAE).

Można również rozważyć wprowadzenie ograniczeń wejściowych dla programu i innych dla systemów 32-bitowych i 64-bitowych.

4

Czy sprawdziłeś, aby upewnić się, że nie masz przecieków w pamięci?

Ponieważ Twój program jest przenośny dla systemu Linux, sugeruję, aby go uruchomić pod Valgrind, aby się upewnić.

+0

Tak, sprawdziłem pod kątem przecieków, nie ma żadnych. – KPexEA

0

Brzmi to tak, jakbyś prowadził konwersację binarną, więc dlaczego potrzebujesz mieć wszystkie dane w pamięci ?.
Nie możesz po prostu odczytać prymitywu z txt (xml), a następnie zapisać na binarystream?

2

Zakładając, że korzystasz z systemu Windows XP, jeśli przekroczyłeś tylko limit pamięci i nie masz ochoty ani czasu na przerobienie kodu zgodnie z powyższymi sugestiami, możesz dodać przełącznik/3GB do pliku boot.ini, a następnie tylko kwestia ustawienia przełącznika linker, aby uzyskać dodatkowe 1 GB pamięci.

+0

Nie jest tak proste korzystanie z 3 GB. Powinieneś upewnić się, że wszystkie twoje arytmetyczne operacje wskaźnika są bezpieczne, inaczej spowodujesz awarię, gdy użycie pamięci stanie się wysokie. Więcej informacji można znaleźć na stronie http://blogs.msdn.com/oldnewthing/archive/2004/08/12/213468.aspx. – eran

3

Podejrzewam, że problemy z pamięcią polegają na przechowywaniu drzewa BSP w pamięci. Więc utrzymuj BSP na dysku i przechowuj tylko kilka porcji w pamięci. Z BSP powinno to być dość łatwe, ponieważ struktura nadaje się bardziej niż inne struktury drzewiaste, a logika powinna być prosta. Aby być zarówno wydajnym, jak i przyjaznym dla pamięci, możesz mieć pamięć podręczną z brudną flagą, przy czym rozmiar pamięci podręcznej jest ustawiony na mniejszą dostępną pamięć dla pokoju do oddychania.

1

Musisz zrozumieć, że pamięć wirtualna różni się od "pamięci RAM" tym, że ilość pamięci wirtualnej, której używasz, jest całkowitą zarezerwowaną kwotą, podczas gdy pamięć rzeczywista (w systemie Windows nazywana jest zestawem roboczym) to pamięć które faktycznie zmodyfikowałeś lub zablokowałeś.

Jak ktoś inny zauważył, na 32-bitowych platformach Windows limit pamięci wirtualnej wynosi 2 gigabajty, chyba że ustawisz specjalną flagę na 3 gigabajty i możesz zapewnić, że wszystkie wskaźniki będą w kodzie i dowolnych wykorzystywanych bibliotekach używaj niepodpisanych wskaźników.

Zmuszanie użytkowników do 64-bitowego monitorowania pamięci wirtualnej i ograniczanie maksymalnego rozmiaru bloku do czegoś, co wygodnie mieści się w granicach narzuconych przez 32-bitowe systemy operacyjne byłoby moją radą.

Uderzyłem się w 32-bitową ścianę w systemie Windows, ale nie mam żadnego doświadczenia w obchodzeniu się z tymi ograniczeniami w Linuksie, więc rozmawiałem tylko o stronie systemu Windows.

1

W 32-bitowym XP maksymalna przestrzeń adresowa programu to 2 GB. Następnie masz fragmentację ze względu na DLL i sterowniki ładowanie się do swojej przestrzeni adresowej. Na koniec masz problem z fragmentacją stosu.

Najlepszym posunięciem jest po prostu przeskoczenie i uruchomienie jako proces 64-bitowy (w systemie 64-bitowym). Nagle wszystkie te problemy ustąpiły. Możesz użyć lepszej sterty, aby złagodzić skutki fragmentacji sterty, i możesz spróbować użyć VirtualAlloc, aby pobrać swoją pamięć w jednym dużym, ciągłym fragmencie (a następnie możesz nim zarządzać!), Aby zniechęcić DLL/sterowniki do jego fragmentacji.

Wreszcie, możesz podzielić swój BSP pomiędzy procesy. Skomplikowane i bolesne, i szczerze mówiąc, po prostu umieszczenie go na dysku byłoby łatwiejsze, ale teoretycznie można uzyskać lepszą wydajność dzięki grupie procesów wymieniających informacje, jeśli możesz zatrzymać wszystko, co rezydentne (i zakładając, że możesz być mądrzejszy od pamięci niż system operacyjny może obsługiwać buforowanie plików ... które jest duże, jeśli). Każdy proces wymagałby znacznie mniej pamięci i dlatego nie powinien być uruchamiany w limicie przestrzeni adresowej 2 GB. Oczywiście, będziesz szybciej wypalać RAM/swap.

Można łagodzić skutki fragmentacji przestrzeni adresowej przez przydzielanie mniejszych porcji. Będzie to miało inne nieprzyjemne skutki uboczne, ale możesz zastosować politykę ograniczania, w której pobierasz mniejsze i mniejsze porcje pamięci, jeśli nie uda ci się pomyślnie przydzielić. Często to proste podejście da ci program, który działa, gdy inaczej by nie było, ale reszta czasu działa tak dobrze, jak to możliwe.

Chłopcze, czy komputery 64-bitowe nie brzmią tak ładnie, jak inne opcje?

1

W jaki sposób przydzielasz pamięć dla punktów? Czy przypisujesz punkt jeden na raz (np. pkt = nowy punkt). Wtedy, w zależności od wielkości punktu, część pamięci może zostać zmarnowana.Na przykład pamięć Windows jest przydzielana w wielokrotnościach 16 bajtów, więc nawet jeśli poprosisz o przydzielenie 1 bajta, system operacyjny faktycznie przydzieli 16 bajtów.

W takim przypadku pomocne może być użycie przydziału pamięci. Możesz wykonać szybką kontrolę przy użyciu przydziału programu STL. (przeładuj nowego operatora dla klasy Point i użyj alokatora STL do alokacji pamięci zamiast "malloc" lub domyślnego nowego operatora).

+0

Przydzielam punkty i polys za pomocą menedżera sterty, więc pobierają tylko tyle miejsca, ile potrzeba i prawie nie są podsłuchane, ponieważ mój stertę alokował (w tym przypadku) 1mb porcji i pobiera z każdego kawałka żądania od – KPexEA

+0

Innym powodem może być "pamięć fragmentacja "(np. pamięć jest dostępna w małych porcjach, ale gdy pytasz o" 1 Mb ", ciągły fragment 1 MB nie jest dostępny Czy parser XML używa" menedżera sterty "? Może być parser XML używa standardowej alokacji pamięci i powodowania fragmentacja? –

0

Jeśli chcesz być niezależny od wielkości pamięci, potrzebujesz niezależnego od rozmiaru algorytmu. Bez względu na wielkość pamięci RAM, jeśli nie masz kontroli nad pamięcią, będziesz wpadać na granicę.

Spójrz na najmniejszą porcję informacji, którą możesz ewentualnie wykorzystać do wytworzenia odrobiny wydruków. Następnie pomyśl o sposobie podzielenia danych wejściowych na fragmenty tej wielkości.

Teraz brzmi łatwo, prawda? (Cieszę się, że nie muszę tego robić :))

1

Może nie być przydzielanie i zwalnianie pamięci w optymalny sposób. Jak zauważyli inni, możesz przeciekać pamięć i nie wiedzieć o tym. Debugowanie i optymalizacja alokacji pamięci zajmie trochę czasu.

Jeśli nie chcesz tracić czasu na optymalizowanie zużycia pamięci, możesz wypróbować wersję Conservative Garbage Collector? Jest to zamiennik wtyczki malloc()/new i free(). W rzeczywistości free() nie jest opcją, więc możesz po prostu usunąć te połączenia z twojego programu. Jeśli zamiast tego ręcznie zoptymalizujesz swój program i uporządkujesz pulę pamięci zgodnie z wcześniejszymi sugestiami, skończy się to wykonywaniem wielu prac, które CGC już dla ciebie wykonuje.

1

Należy przesyłać strumieniowo dane wyjściowe, a także dane wejściowe. Jeśli twój format wyjściowy nie jest zorientowany strumieniowo, pomyśl o wykonaniu drugiego przebiegu. Na przykład, jeśli plik wyjściowy rozpoczyna się od sumy kontrolnej/rozmiaru danych, pozostaw miejsce na pierwszym przejściu i wyszukaj/zapisz w tym miejscu później.

0

Nie musisz przełączać się na maszyny 64-bitowe, ani nie potrzebujesz większości z 1000 rzeczy sugerowanych przez innych. Potrzebny jest bardziej przemyślany algorytm.

Oto kilka rzeczy, które możesz zrobić, aby pomóc w tej sytuacji:

  • Jeśli jesteś na Windows, wykorzystujące plików mapy (sample code). Da to dostęp do pliku za pomocą pojedynczego wskaźnika buforowego, tak jakbyś czytał cały plik w pamięci, tylko bez faktycznego robienia tego. Najnowsze wersje jądra Linux mają podobny mechanizm.
  • Jeśli możesz i wygląda na to, że możesz, przeskanuj plik sekwencyjnie i unikaj tworzenia DOM w pamięci. Spowoduje to znaczne zmniejszenie czasu wczytywania oraz wymagań dotyczących pamięci.
  • Używaj wspólnej pamięci! Prawdopodobnie będziesz mieć wiele drobnych obiektów, takich jak węzły, punkty i inne. Użyj puli pamięci, aby pomóc (zakładam, że używasz niezarządzanego języka. Wyszukaj alokację puli i pule pamięci).
  • Jeśli używasz zarządzanego języka, przynajmniej przenieś tę część do niezarządzanego języka i przejąć kontrolę nad pamięcią i odczytem pliku. Zarządzane języki mają niebagatelny narzut, zarówno pod względem wielkości pamięci, jak i wydajności. (Tak, wiem, że jest oznaczony jako "C++" ...)
  • Próba zaprojektowania algorytmu lokalnego, w którym odczytujesz i przetwarzasz tylko minimalną ilość danych naraz, aby zmniejszyć zapotrzebowanie na pamięć.

Na koniec chciałbym zaznaczyć, że skomplikowane zadania wymagają złożonych działań.Jeśli myślisz, że możesz sobie pozwolić na 64-bitową maszynę z 8 GB pamięci RAM, to po prostu użyj algorytmu "czytaj plik w pamięci, przetwarzaj dane, zapisuj dane wyjściowe", nawet jeśli zajmie to cały dzień.

0

Jest na to dobra technika, polega na przechowywaniu niektórych wystąpień w plikach i po ich uzyskaniu, gdy trzeba z nich korzystać.

Technika ta jest używana przez wiele programów typu open source, takich jak Doxygen, w celu skalowania, gdy wymagana jest duża ilość pamięci.

0

To stara sprawa, ale odkąd ostatnio zrobił to samo ....

Nie ma prostej odpowiedzi. W idealnym świecie używałbyś maszyny z ogromną przestrzenią adresową (tj. 64-bitową) i ogromnymi ilościami pamięci fizycznej. Ogromna przestrzeń adresowa sama w sobie jest niewystarczająca lub po prostu wyrzucona. W takim przypadku przeanalizuj plik XML w bazie danych, a przy odpowiednich zapytaniach wyciągnij to, czego potrzebujesz. Całkiem prawdopodobne jest, że tak właśnie działa OSM (uważam, że świat ma około 330 GB).

W rzeczywistości nadal używam XP 32bit ze względów praktycznych.

To kompromis pomiędzy przestrzenią i prędkością. Możesz zrobić prawie wszystko w dowolnej ilości pamięci, pod warunkiem, że nie obchodzi cię, ile czasu to zajmie. Używając struktur STL możesz parsować wszystko, co chcesz, ale wkrótce zabraknie Ci pamięci. Możesz zdefiniować własne alokatory, które się wymieniają, ale znowu będzie to nieefektywne, ponieważ mapy, wektory, zestawy itp. Nie bardzo wiedzą, co robisz.

Jedynym sposobem, który sprawił, że wszystko działało na niewielkim metrażu na komputerze 32-bitowym, było bardzo uważne rozważenie tego, co robiłem i co było potrzebne, gdy dzieliłem zadanie na kawałki. Pamięć wydajna (nigdy nie używa więcej niż 100 MB), ale nie masowo szybka, ale wtedy nie ma znaczenia - jak często trzeba parsować dane XML?

Powiązane problemy