Dlaczego Garbage Collection, jeśli istnieją inteligentne wskaźniki


67

W dzisiejszych czasach tak wiele języków jest śmieciami. Jest nawet dostępny dla C ++ przez osoby trzecie. Ale C ++ ma RAII i inteligentne wskaźniki. Po co więc używać śmiecia? Czy robi coś ekstra?

A w innych językach, takich jak C #, jeśli wszystkie referencje są traktowane jako inteligentne wskaźniki (z pominięciem RAII), według specyfikacji i implementacji, czy nadal będzie potrzebne zbieranie śmieci? Jeśli nie, to dlaczego tak nie jest?


1
Jedną rzecz zrozumiałem po zadaniu tego pytania - inteligentne wskaźniki potrzebują RAII do zarządzania automatycznym zwolnieniem .
Gulshan,

8
Inteligentne wskaźniki oznaczają użycie RAII dla GC;)
Dario

Heh, c # powinien mieć opcję obsługi wszystkich „odśmiecania” za pomocą RAII. Odwołania cykliczne można wykryć podczas zamykania aplikacji, wszystko, czego potrzebujemy, to zobaczyć, które alokacje są nadal w pamięci po zwolnieniu klasy Program.cs. Następnie odniesienia cykliczne można zastąpić odniesieniami tygodniowymi.
AareP

Odpowiedzi:


67

Po co więc korzystać z wyrzucania elementów bezużytecznych?

Zakładam, że masz na myśli inteligentne wskaźniki zliczane według referencji i zauważę, że są one (podstawową) formą wyrzucania elementów bezużytecznych, więc odpowiem na pytanie „jakie są zalety innych form zbierania elementów bezużytecznych w porównaniu do inteligentnych wskaźników” zamiast.

  • Dokładność . Same liczenie referencyjne przecieka cykle, więc inteligentne wskaźniki referencyjne będą przeciekać pamięć ogólnie, chyba że zostaną dodane inne techniki w celu złapania cykli. Po dodaniu tych technik zniknęła korzyść z prostoty liczenia referencji. Należy również pamiętać, że liczenie i śledzenie referencji na podstawie zakresu gromadzi wartości GC w różnych momentach, czasem liczenie referencji zbiera się wcześniej, a czasami śledzenie GC zbiera się wcześniej.

  • Przepustowość . Inteligentne wskaźniki są jedną z najmniej wydajnych form odśmiecania, szczególnie w kontekście aplikacji wielowątkowych, gdy liczby referencyjne są zderzane atomowo. Istnieją zaawansowane techniki liczenia referencji zaprojektowane w celu złagodzenia tego, ale śledzenie GC nadal jest algorytmem z wyboru w środowiskach produkcyjnych.

  • Latencji . Typowe implementacje inteligentnych wskaźników pozwalają lawinom niszczycieli, co skutkuje nieograniczonymi czasami przerwy. Inne formy zbierania śmieci są znacznie bardziej przyrostowe i mogą nawet odbywać się w czasie rzeczywistym, np. Bieżnia Bakera.


23
Nie mogę uwierzyć, że ta odpowiedź była najważniejsza. Pokazuje całkowity brak zrozumienia inteligentnych wskaźników C ++ i wysuwa twierdzenia, które są tak bardzo niezsynchronizowane z rzeczywistością, że jest to po prostu śmieszne. Po pierwsze, inteligentny wskaźnik, który w dobrze zaprojektowanym fragmencie kodu C ++ będzie dominował, to unikalny wskaźnik, a nie wskaźnik akcji. en.cppreference.com/w/cpp/memory/unique_ptr A po drugie, nie mogę uwierzyć, że rzeczywiście twierdząc korzyści „wyniki” i korzyści w czasie rzeczywistym non deterministyczny zbierania śmieci na inteligentne kursory.
user1703394

4
@ user1703394 Wygląda na to, że odpowiadający miał na myśli wspólne wskaźniki (słusznie lub niesłusznie, nie jestem do końca pewien, co sugerują PO), które są mniej wydajne niż niedeterministyczne odśmiecanie.
Nathan Cooper

8
Są to wszystkie argumenty słomianego człowieka i są poprawne tylko wtedy, gdy całkowicie zignorujesz pytanie lub zignorujesz rzeczywiste wzorce użycia różnych typów inteligentnych wskaźników. Pytanie dotyczyło inteligentnych wskaźników. Tak shared_ptr jest inteligentnym wskaźnikiem, a tak shared_ptr jest najdroższym ze inteligentnych wskaźników, ale nie, nie ma rzeczywistego argumentu za ich wszechobecnym użyciem w pobliżu zbliżania się do uczynienia jakiegokolwiek argumentu wydajnościowego odpowiednim. Poważnie, tę odpowiedź należy przenieść na pytanie dotyczące liczenia referencji. To kiepska referencja licząca odpowiedź na dobre pytanie inteligentnego wskaźnika.
user1703394

4
„Inteligentne wskaźniki nie są szerszą koncepcją”, poważnie? Nie masz pojęcia, jak bardzo to stwierdzenie podważa wszystkie możliwe do tej pory argumenty. Może rzuć okiem na własność Rust i przenieś semantykę: koerbitz.me/posts/… Łatwo jest ludziom z historycznym doświadczeniem z C ++ pominąć fakt, że C ++ 11 / C ++ 14 z jego modelem pamięci, ulepszonym inteligentnym Wskaźniki i semantyka ruchu to zupełnie inna bestia niż ich poprzednicy. Zobacz, jak robi to Rust, jest czystszy niż C ++ i zapewnia świeże spojrzenie.
user1703394

6
@ user1703394: „Nieograniczone pauzy z powodu niszczycieli są niefortunną właściwością RAII używanego do zasobów innych niż pamięć”. Nie, to nie ma nic wspólnego z zasobami niepamięci.
Jon Harrop

63

Ponieważ nikt nie patrzył na to z tego punktu widzenia, sformułuję twoje pytanie: po co wkładać coś w język, jeśli możesz to zrobić w bibliotece? Ignorując konkretne szczegóły implementacyjne i składniowe, wskaźniki GC / smart są w zasadzie szczególnym przypadkiem tego pytania. Po co definiować moduł wyrzucania elementów bezużytecznych w samym języku, jeśli można go zaimplementować w bibliotece?

Istnieje kilka odpowiedzi na to pytanie. Najważniejsze pierwsze:

  1. Zapewniasz, że cały kod może go używać do współdziałania. Myślę, że jest to główny powód, dla którego ponowne użycie i udostępnianie kodu nie zaczęło się tak naprawdę, dopóki Java / C # / Python / Ruby. Biblioteki muszą się komunikować, a jedynym niezawodnym wspólnym językiem, jaki mają, jest to, co znajduje się w samej specyfikacji języka (i do pewnego stopnia w standardowej bibliotece). Jeśli kiedykolwiek próbowałeś ponownie używać bibliotek w C ++, prawdopodobnie doświadczyłeś strasznego bólu, którego nie powoduje żadna standardowa semantyka pamięci. Chcę przekazać strukturę do jakiejś biblioteki lib. Czy przekazuję referencję? Wskaźnik? scoped_ptr?smart_ptr? Czy przekazuję własność, czy nie? Czy istnieje sposób na wskazanie tego? Co jeśli biblioteka musi przydzielić? Czy muszę mu to przydzielić? Nie czyniąc zarządzania pamięcią częścią języka, C ++ zmusza każdą parę bibliotek, aby musiały wynegocjować tutaj swoją własną strategię i naprawdę trudno jest je wszystkie uzgodnić. GC sprawia, że ​​jest to kompletny problem.

  2. Możesz zaprojektować składnię wokół niego. Ponieważ C ++ nie hermetyzuje samego zarządzania pamięcią, musi zapewnić szereg haczyków składniowych, aby kod na poziomie użytkownika mógł wyrażać wszystkie szczegóły. Masz wskaźniki, referencje, constoperatory dereferencji, operatory pośrednie, adres itp. Jeśli przerzucisz zarządzanie pamięcią na sam język, składnię można zaprojektować na podstawie tego. Wszystkie te operatory znikają, a język staje się czystszy i prostszy.

  3. Otrzymujesz wysoki zwrot z inwestycji. Wartość generowana przez dany fragment kodu jest mnożona przez liczbę osób, które go używają. Oznacza to, że im więcej masz użytkowników, tym więcej możesz sobie pozwolić na wydanie oprogramowania. Po przeniesieniu funkcji na język wszyscy użytkownicy języka będą z niej korzystać. Oznacza to, że możesz włożyć w to więcej wysiłku niż w bibliotekę używaną tylko przez podzbiór tych użytkowników. Właśnie dlatego języki takie jak Java i C # mają absolutnie pierwszorzędne maszyny wirtualne i fantastycznie wysokiej jakości śmieciarki: koszty ich rozwoju są amortyzowane przez miliony użytkowników.


Fantastyczna odpowiedź! Gdybym tylko mógł głosować więcej niż raz ...
Dean Harding

10
Warto zauważyć, że wyrzucanie elementów bezużytecznych nie jest w rzeczywistości zaimplementowane w samym języku C #, ale w .NET Framework , a zwłaszcza Common Language Runtime (CLR).
Robert Harvey

6
@RobertHarvey: Nie jest zaimplementowany przez język, ale nie działałby bez współpracy języka. Na przykład kompilator musi zawierać informacje określające, w każdym punkcie kodu, lokalizację każdego rejestru lub przesunięcia ramki stosu, które zawierają odniesienie do odpiętego obiektu. Jest to absolutnie bez wyjątku niezmiennik , którego nie można byłoby utrzymać bez wsparcia językowego.
supercat

Główną zaletą posiadania przez GC obsługi języka i wymaganego frameworka jest to, że zapewnia, że ​​nigdy nie będą istniały żadne odniesienia do pamięci, która mogłaby być przydzielona w innym celu. Jeśli ktoś Disposewywoła obiekt zawierający mapę bitową, każde odwołanie do tego obiektu będzie odwołaniem do umieszczonego obiektu bitmapowego. Jeśli obiekt został przedwcześnie usunięty, podczas gdy inny kod nadal oczekuje jego użycia, klasa bitmap może zapewnić, że inny kod ulegnie awarii w przewidywalny sposób. Natomiast użycie odwołania do zwolnionej pamięci jest zachowaniem niezdefiniowanym.
supercat

34

Odśmiecanie w zasadzie oznacza po prostu, że przydzielone obiekty są automatycznie zwalniane w pewnym momencie, gdy nie są już osiągalne.

Mówiąc dokładniej, są one uwalniane, gdy stają się nieosiągalne dla programu, ponieważ w przeciwnym razie obiekty o odwołaniach cyklicznych nigdy nie zostałyby uwolnione.

Inteligentne wskaźniki odnoszą się tylko do dowolnej struktury, która zachowuje się jak zwykły wskaźnik, ale ma dołączoną dodatkową funkcjonalność. Należą do nich między innymi zwolnienie, ale także kopiowanie przy zapisie, powiązane kontrole, ...

Teraz, jak już wspomniałeś, inteligentnych wskaźników można użyć do zaimplementowania formy wyrzucania elementów bezużytecznych.

Ale tok myślenia idzie w następujący sposób:

  1. Odśmiecanie jest fajną rzeczą, ponieważ jest wygodne i muszę zająć się mniejszą liczbą rzeczy
  2. Dlatego: Chcę zbieranie śmieci w moim języku
  3. Jak mogę wprowadzić GC do mojego języka?

Oczywiście możesz to zaprojektować w ten sposób od samego początku. C # został zaprojektowany do zbierania śmieci, więc po prostu newtwój obiekt i zostanie zwolniony, gdy referencje wypadną poza zakres. To, jak zostanie to zrobione, zależy od kompilatora.

Ale w C ++ nie było przeznaczone wyrzucanie elementów bezużytecznych. Jeśli przydzielimy jakiś wskaźnik int* p = new int;i wypadnie on poza zasięg, psam zostanie usunięty ze stosu, ale nikt nie zajmie się przydzieloną pamięcią.

Teraz jedyne, co masz od samego początku, to deterministyczne niszczyciele . Kiedy obiekt opuszcza zakres, w którym został utworzony, wywoływany jest jego destruktor. W połączeniu z szablonami i przeciążeniem operatora można zaprojektować obiekt otoki, który zachowuje się jak wskaźnik, ale wykorzystuje funkcje destruktora do czyszczenia podłączonych do niego zasobów (RAII). Nazywasz to inteligentnym wskaźnikiem .

Wszystko to jest ściśle specyficzne dla C ++: przeciążenie operatora, szablony, destruktory, ... W tej konkretnej sytuacji językowej opracowałeś inteligentne wskaźniki, które zapewnią ci GC, którego potrzebujesz.

Ale jeśli projektujesz język za pomocą GC od samego początku, jest to jedynie szczegół implementacji. Po prostu mówisz, że obiekt zostanie wyczyszczony, a kompilator zrobi to za ciebie.

Inteligentne wskaźniki, takie jak w C ++, prawdopodobnie nie byłyby nawet możliwe w językach takich jak C #, które nie mają żadnego deterministycznego zniszczenia (C # działa w tym celu, zapewniając cukier składniowy do wywoływania .Dispose()określonych obiektów). Zasoby niereferencyjne zostaną w końcu odzyskane przez GC, ale nie określono, kiedy dokładnie to nastąpi.

A to z kolei może pozwolić GC na bardziej wydajną pracę. Wbudowany głębiej w język niż inteligentne wskaźniki, które są na nim ustawione, .NET GC może np. Opóźniać operacje pamięci i wykonywać je w blokach, aby były tańsze lub nawet przenosić pamięć w celu zwiększenia wydajności w zależności od częstotliwości obiektów są dostępne.


C # ma formę deterministycznego zniszczenia poprzez IDisposablei using. Ale wymaga to trochę wysiłku programisty, dlatego zwykle jest używany tylko do bardzo rzadkich zasobów, takich jak uchwyty połączeń z bazą danych.
JSB ձոգչ

7
@JSBangs: Dokładnie. Podobnie jak C ++ buduje inteligentne wskaźniki wokół RAII, aby uzyskać GC, C # idzie w drugą stronę i buduje „inteligentnych dyspozytorów” wokół GC, aby uzyskać RAII;) W rzeczywistości szkoda, że ​​RAII jest tak trudne w C #, ponieważ jest świetne na wyjątek- bezpieczna obsługa zasobów. Na przykład F # próbuje prostszej IDisposableskładni, po prostu zastępując konwencjonalny let ident = valueprzez use ident = value...
Dario

@Dario: „C # idzie w drugą stronę i buduje„ inteligentnych dyspozytorów ”wokół GC, aby uzyskać RAII”. RAII w C # usingnie ma w ogóle nic wspólnego z odśmiecaniem pamięci, po prostu wywołuje funkcję, gdy zmienna wychodzi poza zakres, tak jak destruktory w C ++.
Jon Harrop,

1
@Jon Harrop: Proszę co? Cytowane przez ciebie stwierdzenie dotyczy zwykłych destruktorów C ++, bez liczenia referencji / inteligentnych wskaźników / odśmiecania.
Dario

1
„Wyrzucanie elementów bezużytecznych oznacza po prostu, że przydzielone obiekty są automatycznie zwalniane, gdy nie są już do nich odwoływane. Dokładniej, są one uwalniane, gdy stają się nieosiągalne dla programu, ponieważ w przeciwnym razie obiekty o odwołaniach cyklicznych nigdy nie zostałyby zwolnione”. ... Bardziej dokładne byłoby powiedzenie, że są one automatycznie zwalniane w pewnym momencie po , a nie kiedy . Zauważ, że gdy implikuje to, że rekultywacja następuje natychmiast, to w rzeczywistości rekultywacja często ma miejsce później.
Todd Lehman,

4

Moim zdaniem istnieją dwie duże różnice między wyrzucaniem elementów bezużytecznych a inteligentnymi wskaźnikami wykorzystywanymi do zarządzania pamięcią:

  1. Inteligentne wskaźniki nie mogą zbierać cyklicznych śmieci; puszka na śmieci
  2. Inteligentne wskaźniki wykonują całą pracę w momencie odwoływania się, dereferencji i zwalniania w wątku aplikacji; wywóz śmieci nie musi

To pierwsze oznacza, że ​​GC będzie zbierać śmieci, których inteligentne wskaźniki nie będą; jeśli używasz inteligentnych wskaźników, musisz unikać tworzenia tego rodzaju śmieci lub być przygotowanym na ręczne radzenie sobie z nimi.

To ostatnie oznacza, że ​​bez względu na to, jak inteligentne są inteligentne wskaźniki, ich działanie spowolni działające wątki w twoim programie. Odśmiecanie może odroczyć pracę i przenieść ją do innych wątków; co pozwala ogólnie być bardziej wydajnym (w rzeczywistości koszt czasu pracy nowoczesnego GC jest niższy niż normalny system Malloc / Free, nawet bez dodatkowego narzutu inteligentnych wskaźników) i rób to, co wciąż musi zrobić, nie wchodząc sposób wątków aplikacji.

Teraz zauważ, że inteligentne wskaźniki, będące konstrukcjami programowymi, mogą być używane do robienia różnego rodzaju innych interesujących rzeczy - patrz odpowiedź Dario - które są całkowicie poza zakresem zbierania śmieci. Jeśli chcesz to zrobić, potrzebujesz inteligentnych wskaźników.

Jednak do celów zarządzania pamięcią nie widzę żadnych perspektyw na inteligentne wskaźniki zastępujące odśmiecanie. Po prostu nie są w tym tak dobrzy.


6
@Tom: spójrz na odpowiedź Dario, aby uzyskać szczegółowe informacje na temat inteligentnych wskaźników. Jeśli chodzi o zalety inteligentnych wskaźników - dezalokacja deterministyczna może być ogromną zaletą, gdy jest używana do kontrolowania zasobów (nie tylko pamięci). W rzeczywistości okazało się to tak ważne, że Microsoft wprowadził usingblok w kolejnych wersjach C #. Co więcej, niedeterministyczne zachowanie GC może być zabraniające w systemach czasu rzeczywistego (dlatego nie stosuje się tam GC). Nie zapominajmy również, że GC są tak skomplikowane, aby naprawić, że większość z nich przecieka pamięć i są dość nieefektywne (np. Boehm…).
Konrad Rudolph,

6
Niedeterminizm GC jest, jak sądzę, odrobiną czerwonego śledzia - istnieją systemy GC, które nadają się do użytku w czasie rzeczywistym (takie jak Recycler IBM), nawet jeśli nie są widoczne w maszynach wirtualnych na komputerach stacjonarnych i serwerach. Ponadto użycie inteligentnych wskaźników oznacza użycie malloc / free, a konwencjonalne implementacje malloc są niedeterministyczne ze względu na potrzebę przeszukiwania darmowej listy. Ruchome systemy GC mają więcej deterministycznych czasów alokacji niż systemy malloc / free, choć oczywiście mniej deterministyczne czasy dezalokacji.
Tom Anderson

3
Jeśli chodzi o złożoność: tak, GC są złożone, ale nie jestem świadomy, że „większość w rzeczywistości przecieka pamięć i jest dość nieefektywna”, i chciałbym zobaczyć pewne dowody w innym przypadku. Boehm nie jest dowodem, ponieważ jest to bardzo prymitywna implementacja i został zbudowany, aby obsługiwać język C, w którym dokładne GC jest zasadniczo niemożliwe z powodu braku bezpieczeństwa typu. To odważny wysiłek, a to, że w ogóle działa, jest bardzo imponujące, ale nie można tego traktować jako przykładu GC.
Tom Anderson,

8
@Jon: zdecydowanie nie bzdury. bugzilla.novell.com/show_bug.cgi?id=621899 lub, bardziej ogólnie: flyingfrogblog.blogspot.com/2009/01/… Jest to dobrze znane i właściwość wszystkich konserwatywnych GC.
Konrad Rudolph,

3
„Koszt działania nowoczesnego GC jest niższy niż w przypadku zwykłego systemu malloc / free”. Czerwony śledź tutaj. Jest tak tylko dlatego, że tradycyjny malloc jest strasznie nieefektywnym algorytmem. Nowoczesne alokatory, które używają wielu segmentów dla różnych rozmiarów bloków, są znacznie szybsze w alokacji, znacznie mniej podatne na fragmentację sterty i nadal zapewniają szybką dezalokację.
Mason Wheeler,

3

Termin wyrzucanie elementów bezużytecznych oznacza, że ​​do zebrania są śmieci. W C ++ inteligentne wskaźniki są dostępne w wielu odmianach, przede wszystkim w unikatowej wersji. Unique_ptr jest w zasadzie pojedynczym konstruktem własności i zakresu. W dobrze zaprojektowanym fragmencie kodu większość zasobów przydzielonych do sterty normalnie znajdowałaby się za inteligentnymi wskaźnikami Unique_ptr, a własność tych zasobów będzie zawsze dobrze zdefiniowana. W Unique_PTR nie ma prawie żadnych kosztów ogólnych, a Unique_Ptr usuwa większość problemów z ręcznym zarządzaniem pamięcią, które tradycyjnie doprowadzały ludzi do języków zarządzanych. Teraz, gdy coraz więcej współbieżnych rdzeni staje się coraz bardziej powszechnym dobrem, zasady projektowania, które skłaniają kod do używania unikalnej i dobrze zdefiniowanej własności w dowolnym momencie, stają się ważniejsze dla wydajności.

Nawet w dobrze zaprojektowanym programie, szczególnie w środowiskach wielowątkowych, nie wszystko można wyrazić bez wspólnych struktur danych, a dla tych struktur danych, które naprawdę tego wymagają, wątki muszą się komunikować. RAII w c ++ działa całkiem nieźle w kwestiach dotyczących życia w konfiguracji jednowątkowej, w konfiguracji wielowątkowej żywotność obiektów może nie być całkowicie hierarchicznie zdefiniowana. W takich sytuacjach użycie shared_ptr stanowi dużą część rozwiązania. Tworzysz współwłasność zasobu, a to w C ++ jest jedynym miejscem, w którym widzimy śmieci, ale przy tak małych ilościach, że właściwie zaprojektowany program c ++ powinien być uważany za bardziej do implementacji kolekcji śmieci ze współdzielonymi ptrami niż pełnymi śmieciami jako zaimplementowane w innych językach. C ++ po prostu nie ma tak dużo „śmieci”

Jak stwierdzili inni, inteligentne wskaźniki liczone w referencjach są jedną z form odśmiecania, a dla tego istnieje jeden poważny problem. Przykładem stosowanym głównie jako wadę zliczania odwołań w postaci śmieci, jest problem z tworzeniem osieroconych struktur danych połączonych ze sobą inteligentnymi wskaźnikami, które tworzą klastry obiektów, które uniemożliwiają się gromadzeniu. Podczas gdy w programie zaprojektowanym zgodnie z modelem obliczeniowym aktora, struktury danych zwykle nie pozwalają na pojawienie się takich nieściągalnych klastrów w C ++, gdy używasz szerokiego współdzielonego podejścia do programowania wielowątkowego, co jest używane głównie w dużej części przemysłu, osierocone klastry mogą szybko stać się rzeczywistością.

Podsumowując, jeśli przez użycie wspólnego wskaźnika masz na myśli szerokie zastosowanie unikalnego_ptr w połączeniu z modelem aktorskim obliczeń do programowania wielowątkowego i ograniczone użycie shared_ptr, niż inne formy zbierania śmieci nie kupują ci żadnych dodatkowe korzyści. Jeśli jednak podejście typu „wszystko do współużytkowania” skończyłoby się udostępnieniem w dowolnym miejscu opcji shared_ptr, należy rozważyć zmianę modeli współbieżności lub przejście na język zarządzany, który jest bardziej ukierunkowany na szersze współdzielenie własności i równoczesny dostęp do struktur danych.


1
Czy to znaczy, Rustże nie trzeba wyrzucać śmieci?
Gulshan

1
@Gulshan Rust jest jednym z niewielu języków, który obsługuje bezpieczne unikalne wskaźniki.
CodesInChaos

2

Większość inteligentnych wskaźników jest implementowana przy użyciu liczenia referencji. Oznacza to, że każdy inteligentny wskaźnik odnoszący się do obiektu zwiększa liczbę odwołań do obiektów. Gdy liczba ta spadnie do zera, obiekt zostaje zwolniony.

Problem występuje, jeśli masz odwołania cykliczne. Oznacza to, że A ma odniesienie do B, B ma odniesienie do C, a C ma odniesienie do A. Jeśli używasz inteligentnych wskaźników, to aby zwolnić pamięć związaną z A, B i C, musisz ręcznie dostać tam „złamać” cykliczne odniesienie (np. używając weak_ptrw C ++).

Odśmiecanie (zwykle) działa całkiem inaczej. Obecnie większość śmieciarek stosuje test osiągalności . Oznacza to, że przegląda wszystkie odwołania na stosie i te, które są globalnie dostępne, a następnie śledzi każdy obiekt, do którego odnoszą się te odwołania, i obiekty, do których się odnoszą itp. Wszystko inne to śmieci.

W ten sposób, referencje okrągłe nie liczą więcej - jak długo ani A, B i C są osiągalne , pamięć może zostać odzyskane.

Są „inne” zalety „rzeczywistego” wyrzucania śmieci. Na przykład przydzielanie pamięci jest wyjątkowo tanie: wystarczy zwiększyć wskaźnik do „końca” bloku pamięci. Dealokacja ma również stały zamortyzowany koszt. Ale oczywiście języki takie jak C ++ pozwalają na implementację zarządzania pamięcią w dowolny sposób, dzięki czemu możesz wymyślić strategię alokacji, która jest jeszcze szybsza.

Oczywiście w C ++ ilość pamięci przydzielanej na stos jest zwykle mniejsza niż język obciążony referencjami, taki jak C # / .NET. Ale to nie jest tak naprawdę kwestia zbierania śmieci w porównaniu do inteligentnych wskaźników.

W każdym razie problem nie jest taki, że wycięcie i wyschnięcie jest lepsze niż drugie. Każdy z nich ma zalety i wady.


2

Chodzi o wydajność . Nieprzydzielenie pamięci wymaga dużo administracji. Jeśli nieprzydzielenie działa w tle, wydajność procesu pierwszoplanowego wzrasta. Niestety przydział pamięci nie może być leniwy (przydzielone obiekty zostaną wykorzystane w świętym momencie), ale uwalnianie obiektów może.

Spróbuj w C ++ (bez GC), aby przydzielić dużą grupę obiektów, wydrukuj „cześć”, a następnie usuń je. Będziesz zaskoczony, jak długo zajmuje uwolnienie obiektów.

Ponadto GNU libc zapewnia bardziej skuteczne narzędzia do nieprzydzielania pamięci, patrz przeszkody . Należy zauważyć, że nie mam doświadczenia z przeszkodami, nigdy ich nie używałem.


Zasadniczo masz rację, ale należy zauważyć, że jest to problem, który ma bardzo proste rozwiązanie: użyj alokatora puli lub alokatora małych obiektów, aby powiązać dezalokacje. Ale to wymaga (nieco) więcej wysiłku niż uruchomienie GC w tle.
Konrad Rudolph,

Tak, oczywiście, GC jest znacznie wygodniejszy. (Szczególnie dla początkujących: nie ma problemów z własnością, nie ma nawet operatora usuwania.)
ern0

3
@ ern0: nie. Cały sens inteligentnych wskaźników (zliczanie referencji) polega na tym, że nie ma problemu z własnością ani operatora usuwania.
Konrad Rudolph,

3
@Jon: co tak naprawdę jest przez większość czasu. Jeśli nie chcesz współdzielić stanu obiektu między różnymi wątkami, będziesz mieć zupełnie inne problemy. Przyznaję, że wiele osób programuje w ten sposób, ale jest to konsekwencja złych abstrakcyjnych wątków, które istniały do ​​niedawna i nie jest to dobry sposób na wielowątkowość.
Konrad Rudolph,

1
Dealokacja często nie jest wykonywana „w tle”, ale raczej zatrzymuje wszystkie wątki na pierwszym planie. Odśmiecanie w trybie wsadowym jest generalnie poprawą wydajności, pomimo wstrzymywania wątków pierwszego planu, ponieważ umożliwia konsolidację niewykorzystanego miejsca. Można oddzielić procesy zbierania śmieci i kompaktowania sterty, ale - szczególnie w ramach, które używają bezpośrednich odniesień, a nie uchwytów - oba są procesami „zatrzymania świata” i często najbardziej praktyczne jest ich wykonanie razem.
supercat

2

Odśmiecanie może być bardziej wydajne - w zasadzie „zwiększa” koszty zarządzania pamięcią i robi to wszystko na raz. Zasadniczo spowoduje to mniejsze zużycie procesora w związku z alokacją pamięci, ale oznacza to, że w pewnym momencie będziesz mieć dużą liczbę operacji alokacji. Jeśli GC nie jest odpowiednio zaprojektowane, może stać się widoczne dla użytkownika jako „pauza”, podczas gdy GC próbuje zwolnić pamięć. Większość nowoczesnych GC bardzo dobrze utrzymuje to niewidoczne dla użytkownika, z wyjątkiem najbardziej niesprzyjających warunków.

Inteligentne wskaźniki (lub dowolny schemat liczenia referencji) mają tę zaletę, że mają miejsce dokładnie wtedy, gdy można oczekiwać od patrzenia na kod (inteligentny wskaźnik wykracza poza zakres, rzecz jest usuwana). Dostajesz małe wybuchy alokacji tu i tam. Zasadniczo możesz poświęcić więcej czasu procesorowi na alokację, ale ponieważ jest on rozłożony na wszystkie rzeczy, które dzieją się w twoim programie, jest mniej prawdopodobne (poza ograniczeniem alokacji jakiejś struktury danych potworów), aby stał się widoczny dla użytkownika.

Jeśli robisz coś, co ma znaczenie w przypadku responsywności, sugeruję, aby inteligentne liczenie wskaźników / odniesień dało Ci znać dokładnie, kiedy coś się dzieje, abyś mógł wiedzieć podczas kodowania tego, co prawdopodobnie stanie się widoczne dla użytkowników. W ustawieniach GC masz tylko najbardziej efemeryczną kontrolę nad śmieciarzem i po prostu musisz spróbować obejść ten problem.

Z drugiej strony, jeśli Twoim celem jest ogólna przepustowość, system oparty na GC może być znacznie lepszym wyborem, ponieważ minimalizuje zasoby potrzebne do zarządzania pamięcią.

Cykle: Nie uważam problemu cykli za znaczący. W systemie, w którym masz inteligentne wskaźniki, dążysz do struktur danych, które nie mają cykli, lub po prostu jesteś ostrożny, jak puścić takie rzeczy. W razie potrzeby można użyć obiektów posiadaczy, które potrafią przerwać cykle w posiadanych obiektach, aby automatycznie zapewnić odpowiednie zniszczenie. W niektórych obszarach programowania może to być ważne, ale w większości codziennych zadań nie ma znaczenia.


1
„w pewnym momencie będziesz mieć do czynienia z dużą serią działań związanych z alokacją”. Bieżnia Bakera jest przykładem pięknie przyrostowego pojemnika na śmieci. memorymanagement.org/glossary/t.html#treadmill
Jon Harrop

1

Ograniczeniem numer jeden inteligentnych wskaźników jest to, że nie zawsze pomagają one w stosowaniu referencji cyklicznych. Na przykład masz obiekt A przechowujący inteligentny wskaźnik do obiektu B, a obiekt B przechowuje inteligentny wskaźnik do obiektu A. Jeśli zostaną one pozostawione razem bez resetowania któregokolwiek ze wskaźników, nigdy nie zostaną zwolnione.

Dzieje się tak, ponieważ inteligentny wskaźnik musi wykonać określoną akcję, która nie będzie przetwarzana w powyższym scenariuszu, ponieważ oba obiekty są nieosiągalne dla programu. Wyrzucanie elementów bezużytecznych sobie poradzi - prawidłowo rozpozna, że ​​obiekty nie są dostępne do programu i zostaną zebrane.


1

To spektrum .

Jeśli nie chcesz mieć ograniczonej wydajności i jesteś przygotowany na grindowanie, skończysz na zgromadzeniu lub c, z całym obowiązkiem ciebie do podjęcia właściwych decyzji i swobodą robienia tego, ale z tym , cała swoboda, żeby to zepsuć:

„Powiem ci, co masz robić, robisz to. Zaufaj mi”.

Śmieci to drugi koniec spektrum. Masz bardzo małą kontrolę, ale dbasz o nią:

„Powiem ci, czego chcę, spraw, aby to się stało”.

Ma to wiele zalet, głównie dlatego, że nie musisz być tak godny zaufania, jeśli chodzi o dokładną znajomość, kiedy zasób nie jest już potrzebny, ale (pomimo niektórych pływających tutaj odpowiedzi) nie jest dobry dla wydajności, i przewidywalność wydajności. (Podobnie jak wszystkie rzeczy, jeśli otrzymujesz kontrolę i robisz coś głupiego, możesz mieć gorsze wyniki. Jednak sugerowanie, że wiedza w czasie kompilacji, jakie są warunki dla uwolnienia pamięci, nie może być wykorzystana jako wygrana wydajnościowa poza naiwnością).

RAII, określanie zakresu, liczenie odwołań itp. Pomagają w poruszaniu się dalej po tym spektrum, ale nie jest tak daleko. Wszystkie te rzeczy nadal wymagają aktywnego użycia. Nadal pozwalają i wymagają interakcji z zarządzaniem pamięcią w sposób, w jaki nie działa wyrzucanie elementów bezużytecznych.


0

Pamiętaj, że ostatecznie wszystko sprowadza się do wykonania instrukcji przez procesor. Według mojej wiedzy wszystkie procesory klasy konsumenckiej mają zestawy instrukcji, które wymagają przechowywania danych w danym miejscu w pamięci oraz wskaźników do tych danych. To wszystko, co masz na poziomie podstawowym.

Wszystko poza tym, że ma miejsce wyrzucanie elementów bezużytecznych, odwołania do danych, które mogły zostać przeniesione, zagęszczanie sterty itp. Itd. Działa w ramach ograniczeń określonych przez powyższy paradygmat „fragmentu pamięci ze wskaźnikiem adresu”. To samo dotyczy inteligentnych wskaźników - WCIĄŻ musisz uruchomić kod na rzeczywistym sprzęcie.

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.