Analizowanie użycia pamięci: Java vs C ++ Znikoma?


9

W jaki sposób wykorzystanie pamięci przez obiekt całkowity napisany w Javie porównuje \ wykorzystanie pamięci przez obiekt całkowity napisany w C ++? Czy różnica jest znikoma? Bez różnicy? Duża różnica? Zgaduję, że jest tak samo, ponieważ int jest int niezależnie od języka (?)

Powodem, dla którego o to zapytałem, jest to, że czytałem o tym, jak ważne jest wiedzieć, kiedy wymagania dotyczące pamięci programu zapobiegną rozwiązaniu danego problemu przez programistę.

Zafascynowała mnie ilość pamięci potrzebnej do stworzenia pojedynczego obiektu Java. Weźmy na przykład obiekt liczb całkowitych. Popraw mnie, jeśli się mylę, ale obiekt całkowity Java wymaga 24 bajtów pamięci:

  • 4 bajty dla zmiennej int inst
  • 16 bajtów narzutu (odwołanie do klasy obiektu, informacje na temat czyszczenia pamięci i informacje o synchronizacji)
  • 4 bajty wypełnienia

Jako kolejny przykład tablica Java (która jest implementowana jako obiekt) wymaga ponad 48 bajtów:

  • 24 bajty informacji o nagłówku
  • 16 bajtów narzutu obiektu
  • 4 bajty długości
  • 4 bajty na wypełnienie
  • plus pamięć potrzebna do przechowywania wartości

Jak porównują te zastosowania pamięci z tym samym kodem napisanym w C ++?

Kiedyś byłem nieświadomy użycia pamięci przez programy C ++ i Java, które napisałem, ale teraz, gdy zaczynam się uczyć o algorytmach, bardziej doceniam zasoby komputera.


6
Co to jest „obiekt całkowity” w C ++? int? Jeśli tak, to powinieneś porównać to z intJavą, Integero ile nie masz 32-bitowych int.
Mat

+1 Jeśli utworzyłem klasę c ++, która miała tylko jedną zmienną int, utworzyłem instancję
Anthony

3
int nie jest int - zależy od platformy

1
C ++ z tylko jednym elementem int zwykle nie będzie miał narzutu. Zajmie dokładnie tyle miejsca, ile platforma używa do przechowywania wartości int (zwykle 4 bajty na obecnych platformach PC).
Dirk Holsopple,

+1 dla programisty Java, który jest ciekawy pamięci. Przede wszystkim świadomość pamięci jest najważniejszym czynnikiem determinującym wydajność współczesnych architektur.
imallett

Odpowiedzi:


15

To zależy od platformy i implementacji.

C ++ gwarantuje, że rozmiar charma dokładnie jeden bajt i co najmniej 8 bitów szerokości. Zatem rozmiar a short intjest co najmniej 16 bitów i nie mniejszy niż char. Rozmiar intjest co najmniej tak duży jak rozmiar short int. Rozmiar long intto co najmniej 32 bity i nie mniej niż int.

sizeof(char) == 1; sizeof(long int) >= sizeof(int) >= sizeof(short int) >= sizeof(bool) >= sizeof(char).

Rzeczywisty model pamięci C ++ jest jednak bardzo zwarty i przewidywalny . Na przykład nie ma metadanych w obiektach, tablicach ani wskaźnikach. Struktury i klasy są ciągłe, tak jak tablice, ale wypełnienie można umieścić w razie potrzeby i potrzeby.

Szczerze mówiąc, takie porównanie jest w najlepszym razie głupie, ponieważ użycie pamięci Java zależy bardziej od implementacji Java niż od uruchamianego kodu.


1
To prawda, ale dla porównania powiedziałbym, że powinniśmy przyjmować typy liczb całkowitych o jednakowej wielkości (nawet jeśli są one dostępne tylko pod różnymi nazwami). W końcu różne rozmiary oznaczają inną semantykę, a na wielu (nie wszystkich) wspólnych platformach rozmiary są identyczne lub liczby całkowite tego samego rozmiaru są dostępne pod różnymi nazwami.

Uwaga do OP: Być może lepiej będzie wybrać wielkość całkowitą - jeśli chcesz 32-bitową liczbę całkowitą w C ++, możesz użyć int32_t.
K.Steff,

9

Większość odpowiedzi wydaje się ignorować kilka dość istotnych kwestii.

Po pierwsze, w ogromnej ilości Java, praktycznie nigdy nie widzisz surowego int- prawie wszystkie zastosowania są Integer, więc fakt, że intmoże być (mniej więcej) taki sam jak intw C lub C ++, jest prawie nieistotny, z wyjątkiem tego ( z mojego doświadczenia wynika, że ​​mały) procent kodu, który używa tylko intzamiast Integer.

Po drugie, rozmiary poszczególnych obiektów nie mają prawie nic wspólnego z powierzchnią pamięci programu jako całości. W Javie ślad pamięci programu dotyczy przede wszystkim tego, jak tuning został wyrzucony śmieci. W większości przypadków GC jest dostrojony, aby zmaksymalizować prędkość, co (w dużej mierze) oznacza uruchamianie GC tak rzadko, jak to możliwe.

W tej chwili nie mam przydatnego linku, ale były pewne testy pokazujące, że Java może działać z tą samą prędkością co C, ale aby to zrobić, musisz uruchamiać GC tak rzadko, że używa około 7 razy więcej pamięć. Nie dlatego, że poszczególne obiekty są 7 razy większe, ale dlatego, że GC może stać się dość drogi, jeśli robisz to zbyt często. Co gorsza, GC może zwolnić pamięć tylko wtedy, gdy może „udowodnić”, że nie ma już żadnego sposobu dostępu do obiektu, a nie po prostu, gdy wiesz, że skończyłeś go używać. Oznacza to, że nawet jeśli uruchamiasz GC znacznie częściej, aby zminimalizować zużycie pamięci, prawdopodobnie nadal możesz planować, że typowy program ma większy obszar pamięci. W takim przypadku możesz zmniejszyć współczynnik do 2 lub 3 zamiast 7. Nawet jeśli drastycznie przesadzisz, nie rób tego1 .

W zależności od sytuacji istnieje inny czynnik, który może, ale nie musi być znaczący: pamięć zajmowana przez samą JVM. Jest to mniej więcej ustalone, więc jako wartość procentowa może być ogromna, jeśli aplikacja sama nie potrzebuje dużo pamięci, lub może być niewielka, jeśli aplikacja musi dużo przechowywać. Przynajmniej na mojej maszynie nawet najbardziej trywialna aplikacja Java wydaje się zajmować około 20-25 megabajtów (może być ponad 1000 razy w przypadku programów trywialnych lub prawie niezmiernie mała w przypadku dużych).


1 Nie oznacza to, że nikt nie byłby w stanie napisać Javy z śladem tak zbliżonym do tego, co można uzyskać w C ++. Trzeba tylko powiedzieć, że samo posiadanie tej samej liczby / wielkości obiektów i częste uruchamianie GC z reguły cię tam nie doprowadzi.


7
Jeśli chodzi o twój pierwszy punkt: nie jestem facetem od Java, ale interfejsy API Java, których widziałem, nigdy nie używały Integer(dlaczego?) int. Tylko kolekcje ogólne nie mają innego wyboru, jak użyć Integerze względu na wymazanie typu, ale jeśli Ci zależało, możesz je zastąpić implementacją specjalizowaną dla intdowolnego pierwotnego typu, którego potrzebujesz. A potem jest tymczasowe boksowanie do przekazywania przez ogólny kod owijania (np. Wszystko, co wymaga Object[]). Poza tym, czy masz źródła narzutów kosmicznych GC? Tak naprawdę nie wątpię, jestem tylko ciekawy.


9

Mam nadzieję, że zdajesz sobie sprawę, że wszystko to jest ściśle zdefiniowane w implementacji, zarówno dla Java, jak i C ++. To powiedziawszy, model obiektowy Java wymaga sporo miejsca.

Obiekty C ++ (generalnie) nie potrzebują żadnej pamięci poza tym, czego potrzebują członkowie. Zauważ, że (w przeciwieństwie do Javy, gdzie wszystko, co zdefiniowane przez użytkownika jest typem referencyjnym), kod klienta może wykorzystywać obiekty zarówno jako typ wartości, jak i typy referencyjne, tzn. Obiekt może przechowywać wskaźnik / referencję do innego obiektu lub przechowywać obiekt bezpośrednio bez pośrednictwa. Jeden dodatkowy wskaźnik na obiekt jest konieczny, jeśli istnieją jakieś virtualmetody, ale całkiem sporo użytecznych klas zaprojektowano tak, aby radziły sobie bez polimorfizmu i nie potrzebują tego. Brak metadanych GC i blokady dla poszczególnych obiektów. Zatem class IntWrapper { int x; public: IntWrapper(int); ... };obiekty nie potrzebują więcej miejsca niż zwykłe ints i mogą być umieszczane bezpośrednio (tj. Bez pośrednictwa) w kolekcjach i innych obiektach.

Tablice są trudne, ponieważ nie ma gotowego, wspólnego odpowiednika tablicy Java w C ++. Możesz po prostu przydzielić wiązkę obiektów new[](bez absolutnie żadnych narzutów / metadanych), ale nie ma pola długości - implementacja prawdopodobnie przechowuje jeden, ale nie masz do niego dostępu. std::vectorjest tablicą dynamiczną, a zatem ma dodatkowe obciążenie i większy interfejs. std::arrayi tablice w stylu C (int arr[N];), potrzebujesz stałej czasowej kompilacji. Teoretycznie powinna to być tylko pamięć obiektu plus jedna liczba całkowita na długość - ale ponieważ możesz uzyskać dynamiczne zmiany rozmiaru i w pełni funkcjonalny interfejs z bardzo małą dodatkową przestrzenią, po prostu idziesz do tego w praktyce. Zauważ, że wszystkie te, a także wszystkie inne kolekcje, domyślnie przechowują obiekty według wartości, oszczędzając w ten sposób pośredniość i miejsce na referencje oraz poprawiając zachowanie pamięci podręcznej. Musisz jawnie przechowywać wskaźniki (inteligentne, proszę), aby uzyskać pośrednie.

Powyższe porównania nie są do końca uczciwe, ponieważ niektóre z tych oszczędności wynikają z nieuwzględnienia funkcji zawartych w Javie, a ich odpowiednik C ++ jest często mniej zoptymalizowany niż odpowiednik Java (*). Powszechny sposób implementacji virtualw C ++ nakłada dokładnie tyle samo kosztów ogólnych, co powszechny sposób implementacji virtualw Javie. Aby uzyskać blokadę, potrzebujesz w pełni funkcjonalnego obiektu mutex, który najprawdopodobniej jest większy niż kilka bitów. Aby uzyskać liczenie referencji ( nieodpowiednik GC i nie powinien być używany jako taki, ale czasem przydatny), potrzebujesz inteligentnego wskaźnika, który dodaje pole zliczania referencji. O ile obiekt nie jest skonstruowany ostrożnie, licznik referencji, obiekt inteligentnego wskaźnika i obiekt odniesienia znajdują się w całkowicie oddzielnych lokalizacjach, a nawet jeśli zbudujesz go poprawnie, wspólny wskaźnik może (musi?) Nadal mieć dwa wskaźniki zamiast jednego. Z drugiej strony, dobry styl C ++ nie wykorzystuje tych funkcji na tyle, aby miało to znaczenie - w praktyce dobrze napisane obiekty biblioteki C ++ zużywają mniej. To niekoniecznie oznacza mniejsze zużycie pamięci, ale oznacza, że ​​C ++ ma dobry start w tym zakresie.

(*) Na przykład można uzyskać wirtualne połączenia, kody skrótu tożsamości i blokowanie za pomocą tylko jednego słowa dla niektórych obiektów (i dwóch słów dla wielu innych obiektów) poprzez połączenie informacji o typie z różnymi flagami i usunięcie bitów blokady dla obiektów, które są prawdopodobnie nie będzie potrzebował zamków. Zobacz efektywne pod względem miejsca i czasu wdrożenie Java Object Model (PDF) autorstwa Davida F. Bacona, Stephena J. Finka i Davida Grove'a, aby uzyskać szczegółowe wyjaśnienie tej i innych optymalizacji.


3

Zwykły int, w java, zajmuje dokładnie tyle samo miejsca, co intw C ++, pod warunkiem, że obie implementacje używają tego samego rozmiaru liczby całkowitej i wyrównania pamięci.

Int „obiekt” ( liczba całkowita w ramce , czyli instancja klasy Integer), przenosi cały narzut instancji klasy w Javie, więc jest znacznie większy niż intw C ++. Jeśli jednak chcesz wyposażyć obiekt w C ++ w te same funkcje, które są dostarczane z obiektami Java od razu po wyjęciu z pudełka (polimorfizm, boks, wyrzucanie elementów bezużytecznych, RTTI), prawdopodobnie uzyskasz obiekt równy rozmiar.

A potem są kwestie optymalizacji; ponieważ modele wykonania i paradygmaty programowania różnią się, jest mało prawdopodobne, aby każdy nietrywialny problem został rozwiązany tak samo w obu językach, więc porównywanie wielkości pamięci na tym poziomie nie ma większego sensu.

Tak, obiekty Java domyślnie niosą narzut więcej niż klasy C ++, ale mają więcej funkcji, co prowadzi do innego stylu programowania - dobry programista może wykorzystać zalety obu stron.


+1 Więcej narzutu, ale więcej funkcji w Javie, rozumiem teraz, dzięki
Anthony
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.