Wady zarządzania pamięcią w oparciu o zakres

38

Naprawdę lubię zarządzanie pamięcią oparte na zakresie (SBMM) lub RAII , ponieważ jest ono częściej (myląco?) Określane przez społeczność C ++. O ile mi wiadomo, z wyjątkiem C ++ (i C), nie ma dziś w użyciu żadnego innego głównego nurtu, który uczyniłby SBMM / RAII głównym mechanizmem zarządzania pamięcią, a zamiast tego wolą używać odśmiecania (GC).

Od tego czasu uważam to za dość mylące

  1. SBMM sprawia, że ​​programy są bardziej deterministyczne (możesz dokładnie powiedzieć, kiedy obiekt jest niszczony);
  2. w językach korzystających z GC często trzeba ręcznie zarządzać zasobami (patrz na przykład zamykanie plików w Javie), co częściowo obala cel GC i jest również podatne na błędy;
  3. Pamięć sterty może również (bardzo elegancko, imo) być ograniczona zasięgiem (patrz std::shared_ptrw C ++).

Dlaczego SBMM nie jest szerzej stosowany? Jakie są jego wady?

Paweł
źródło
1
Niektóre wady (szczególnie dotyczące szybkości) są omówione w Wikipedii: en.wikipedia.org/wiki/…
Philipp
2
Ręczny problem zarządzania zasobami Java jest efektem ubocznym braku gwarancji, że finalize()metoda obiektu zostanie wywołana przed odśmiecaniem. W efekcie tworzy to tę samą klasę problemu, którą ma rozwiązać czyszczenie pamięci.
Blrfl,
7
@Blrfl Nonsense. Preferowane byłoby „ręczne” zarządzanie zasobami (oczywiście dla zasobów innych niż pamięć), nawet jeśli ten „problem” nie istniał, ponieważ GC może działać bardzo długo po tym, jak zasób nie będzie używany, lub nawet w ogóle nie będzie działać. To nie jest problem z pamięcią , a zarządzanie pamięcią to wszystko, co ma rozwiązać śmieci.
4
btw. Lubię nazywać to SBRM, ponieważ możesz używać tego samego mechanizmu do ogólnego zarządzania zasobami, a nie tylko pamięcią.
PlasmaHH

Odpowiedzi:

27

Zacznijmy od postulowania, że ​​pamięć jest zdecydowanie (dziesiątki, setki, a nawet tysiące razy) bardziej powszechna niż wszystkie inne zasoby razem wzięte. Każda pojedyncza zmienna, obiekt, członek obiektu potrzebuje pewnej pamięci przydzielonej do niej i zwolnionej później. Dla każdego otwieranego pliku tworzysz dziesiątki do milionów obiektów do przechowywania danych wyciągniętych z pliku. Każdy strumień TCP idzie w parze z nieograniczoną liczbą tymczasowych ciągów bajtów utworzonych do zapisu w strumieniu. Czy jesteśmy na tej samej stronie tutaj? Wspaniały.

Aby RAII działało (nawet jeśli masz gotowe inteligentne wskaźniki dla każdego przypadku użycia pod słońcem), musisz uzyskać prawo własności . Musisz przeanalizować, kto powinien być właścicielem tego lub innego obiektu, a kto nie, a kiedy własność powinna zostać przeniesiona z A do B. Jasne, możesz użyć współwłasności do wszystkiego , ale wtedy będziesz emulować GC za pomocą inteligentnych wskaźników. W tym momencie staje się znacznie łatwiejsze i szybsze wbudowanie GC w język.

Wyrzucanie elementów bezużytecznych uwalnia cię od troski o najczęściej używany zasób - pamięć. Jasne, nadal musisz podjąć tę samą decyzję w odniesieniu do innych zasobów, ale są one znacznie mniej powszechne (patrz wyżej), a skomplikowane (np. Wspólne) własności również są mniej powszechne. Obciążenie psychiczne jest znacznie zmniejszone.

Teraz nazywasz niektóre wady, aby wszystkie wartości były usuwane. Jednak integracja zarówno bezpiecznego dla pamięci GC, jak i typów wartości z RAII w jednym języku jest niezwykle trudna, więc może lepiej jest zmusić te kompromisy za pomocą innych środków?

Utrata determinizmu okazuje się w praktyce nie taka zła, ponieważ wpływa tylko na żywotność obiektu deterministycznego . Jak opisano w następnym akapicie, większość zasobów (oprócz pamięci, która jest obfita i może być przetwarzana raczej leniwie), nie jest zobowiązana do życia obiektów w tych językach. Istnieje kilka innych przypadków użycia, ale z mojego doświadczenia są rzadkie.

Drugi punkt, ręczne zarządzanie zasobami, jest obecnie rozwiązywany za pomocą instrukcji, która wykonuje czyszczenie na podstawie zakresu, ale nie łączy tego czyszczenia z czasem życia obiektu (a zatem nie wchodzi w interakcje z GC i bezpieczeństwem pamięci). To jest usingw C #, withw Pythonie, try-with-resources w najnowszych wersjach Java.


źródło
1
To nie wyjaśnia, dlaczego modele niedeterministyczne, takie jak GC, powinny być lepsze od modeli deterministycznych. usinginstrukcje są możliwe tylko lokalnie. W ten sposób nie można oczyścić zasobów przechowywanych w zmiennych członkowskich.
Philipp
8
@Philipp Współwłasność dla wszystkiego jest GC, tylko bardzo słaba. Jeśli weźmiesz „współwłasność”, co oznacza naliczanie referencji, powiedz to, ale kontynuuj dyskusję na temat cykli w komentarzach do odpowiedzi amona. Nie jestem również pewien, czy zliczanie referencji jest deterministyczne w tym sensie, że zainteresowane jest OP (obiekty są uwalniane tak wcześnie, jak to możliwe, pomijając cykle, ale często nie można tego stwierdzić, patrząc na program). Co więcej, liczenie referencji dla wszystkiego jest powolne, znacznie wolniejsze niż nowoczesne śledzenie GC.
16
usingjest żartem w porównaniu do RAII, tak po prostu wiesz.
DeadMG
3
@Philipp opisz swoje dane dla „przełożonego”. Prawdą jest, że ręczne zarządzanie pamięcią jest szybsze w czasie wykonywania, jeśli chodzi o zarządzanie pamięcią. Jednak kosztów oprogramowania nie można oceniać wyłącznie na podstawie czasu procesora poświęconego wyłącznie na zarządzanie pamięcią.
ArTs,
2
@ArTs: Nawet nie musiałbym się z tym zgodzić. RAII zawiera wymóg, aby obiekt musiał zostać zniszczony, ponieważ wychodzi poza zasięg. Zatem pętla jest wymagana do zniszczenia obiektu. W dzisiejszej generacji GC zniszczenia te można było odłożyć do końca pętli, a nawet później, i wykonać tylko jedną operację, aby zniszczyć setki iteracji wartych pamięci. GC może być bardzo szybki w dobrych przypadkach.
Phoshi,
14

RAII wynika również z automatycznego zarządzania pamięcią zliczania referencji, np. Stosowanego przez Perla. Chociaż zliczanie referencji jest łatwe do wdrożenia, deterministyczne i dość wydajne, nie może poradzić sobie z referencjami cyklicznymi (powodują wyciek), dlatego nie jest powszechnie używane.

Języki odśmiecane nie mogą bezpośrednio używać RAII, ale często oferują składnię z równoważnym efektem. W Javie mamy instrukcję try-with-ressource

try (BufferedReader br = new BufferedReader(new FileReader(path))) { ... }

który automatycznie wywołuje .close()zasób przy wyjściu z bloku. C # ma IDisposableinterfejs, który pozwala .Dispose()na wywołanie przy pozostawieniu using (...) { ... }instrukcji. Python ma withinstrukcję:

with open(filename) as f:
    ...

który działa w podobny sposób. W interesujący sposób metoda otwierania plików Ruby otrzymuje wywołanie zwrotne. Po wykonaniu wywołania zwrotnego plik jest zamykany.

File.open(name, mode) do |f|
    ...
end

Myślę, że Node.js używa tej samej strategii.

amon
źródło
4
Korzystanie z funkcji wyższego rzędu w zarządzaniu zasobami sięga dużo wcześniej niż Ruby. W Lisps dość powszechne jest, powiedzmy, with-open-filehandlefunkcje, które otwierają plik, dają funkcję i po powrocie funkcji ponownie zamykają plik.
Jörg W Mittag
4
Cykliczny argument referencyjny jest dość powszechny, ale jak ważny jest naprawdę? Cykliczne odniesienia można złagodzić za pomocą słabych wskaźników, jeśli własność jest wyraźna.
Philipp
2
@Philipp Gdy używasz liczenia referencji, własność zwykle nie jest jasna. Również ta odpowiedź mówi o językach, które używają liczenia referencji wyłącznie i automatycznie, więc słabe referencje albo nie istnieją, albo są znacznie trudniejsze w użyciu niż silne referencje.
3
@Philipp cykliczne struktury danych są bardzo rzadkie, chyba że i tak pracujesz ze skomplikowanymi wykresami. Słabe wskaźniki nie pomagają w ogólnym grafie obiektów cyklicznych, chociaż pomagają w bardziej powszechnych przypadkach, takich jak wskaźniki nadrzędne w drzewie. Dobrym rozwiązaniem jest utrzymanie obiektu kontekstowego, który reprezentuje odniesienie do całego wykresu i zarządza zniszczeniem. Przeliczanie rachunków nie jest pośrednikiem, ale wymaga, aby programista był bardzo świadomy swoich ograniczeń. To znaczy, że ma nieco wyższy koszt poznawczy niż GC.
amon
1
Ważnym powodem, dla którego liczenie odwołań jest rzadko stosowane, jest to, że jest ono często wolniejsze niż GC, pomimo swojej prostoty.
Rufflewind
14

Moim zdaniem najbardziej przekonującą zaletą wyrzucania elementów bezużytecznych jest to, że pozwala ono na kompozycję . Poprawność zarządzania pamięcią jest lokalną własnością w środowisku śmieci. Możesz spojrzeć na każdą część w izolacji i ustalić, czy może wyciekać pamięć. Połącz dowolną liczbę części z poprawną pamięcią i pozostaną one prawidłowe.

Kiedy polegasz na liczeniu referencji, tracisz tę właściwość. To, czy Twoja aplikacja może przeciekać, staje się globalną własnością całej aplikacji z liczeniem referencji. Każda nowa interakcja między częściami ma możliwość użycia niewłaściwej własności i zarządzania pamięcią.

Ma to bardzo widoczny wpływ na projektowanie programów w różnych językach. Programy w językach GC są zwykle bardziej zupami obiektów z dużą ilością interakcji, podczas gdy w językach bez GC zwykle preferuje się części strukturalne ze ściśle kontrolowanymi i ograniczonymi interakcjami między nimi.

Patrick
źródło
1
Poprawność można skomponować tylko wtedy, gdy obiekty przechowują odniesienia tylko w swoim własnym imieniu, a nie w imieniu celów tych odniesień. Kiedy rzeczy takie jak powiadomienia wchodzą w skład miksu (Bob trzyma odniesienie do Joe, ponieważ Joe poprosił go, aby powiadomił go, gdy coś się wydarzyło, a Bob obiecał to zrobić, ale Bob inaczej nie przejmuje się Joe), poprawność GC często wymaga zarządzania zasobami o określonym zasięgu [wdrażane ręcznie w wielu przypadkach, ponieważ w systemach GC brakuje automatyzacji C ++].
supercat
@supercat: „Poprawność GC często wymaga zarządzania zasobami o określonym zasięgu”. Co? Zakres istnieje tylko w kodzie źródłowym, a GC istnieje tylko w czasie wykonywania (i dlatego jest całkowicie nieświadomy istnienia zakresu).
Jon Harrop,
@JonHarrop: Użyłem terminu „zakres” w tym samym sensie, co „wskaźnik zakresowy” C ++ [czas życia obiektu powinien być taki, jak kontenera go przechowującego], ponieważ jest to użycie sugerowane przez pierwotne pytanie. Chodzi mi o to, że obiekty tworzą potencjalnie długo żyjące odniesienia do siebie do celów takich jak odbieranie zdarzeń, które mogą nie być możliwe do skomponowania w systemie wyłącznie GC. Aby zapewnić poprawność, niektóre odniesienia muszą być mocne, a niektóre muszą być słabe, a które muszą być zależne od sposobu użycia obiektu. Na przykład ...
supercat
... załóżmy, że obiekty Fred i Barney rejestrują się w celu powiadamiania o każdej modyfikacji dowolnego katalogu. Program obsługi Freda dokonuje tylko przyrostu licznika, którego wartość może zgłosić na żądanie, ale do którego nie ma innego zastosowania. Program obsługi Barneya wyświetli nowe okno, jeśli określony plik zostanie zmodyfikowany. Dla poprawności Fred powinien zostać zasubskrybowany słabym zdarzeniem, ale Barney powinien być silny, ale obiekt timera nie będzie w stanie tego wiedzieć.
supercat
@supercat: Racja. Nie powiedziałbym, że zdarza się to „często”. Natknąłem się na to tylko raz na 30 lat programowania.
Jon Harrop,
7

Zamknięcia są istotną cechą prawie wszystkich współczesnych języków. Są bardzo łatwe do wdrożenia za pomocą GC i bardzo trudne (choć nie niemożliwe), aby uzyskać poprawne działanie z RAII, ponieważ jedną z ich głównych cech jest to, że pozwalają ci abstrakcji przez cały okres życia twoich zmiennych!

C ++ dostał je dopiero 40 lat po tym, jak wszyscy to zrobili, i wielu inteligentnym ludziom zajęło dużo ciężkiej pracy, aby je poprawnie. Natomiast wiele języków skryptowych zaprojektowanych i wdrożonych przez osoby o zerowej wiedzy w zakresie projektowania i implementacji języków programowania ma je.

Jörg W Mittag
źródło
9
Nie sądzę, aby zamknięcia w C ++ były dobrym przykładem. Lambdas w C ++ 11 to po prostu cukier syntaktyczny dla klas funktorów (znacznie wcześniejszych niż C ++ 11) i są równie niebezpieczne pod względem pamięci: jeśli przechwycisz coś przez odniesienie i wywołasz zamknięcie po tym, jak coś się skończy, po prostu dostajesz UB, podobnie jak trzymanie referencji dłuższej niż ważna. To, że pojawili się 40 lat później, wynika z opóźnionego uznania FP, a nie z tego, jak wymyślić, jak zapewnić im bezpieczeństwo. I choć ich projektowanie było z pewnością ogromnym zadaniem, wątpię, aby większość wysiłku poświęcono na rozważania na temat życia.
Zgadzam się z delnan: C ++ nie uzyskał poprawnie zamknięć: musisz je bardzo ostrożnie zaprogramować, jeśli nie chcesz uzyskać zrzutu podstawowego podczas ich wywoływania.
Giorgio
2
@delnan: lambda przechwytująca przez referencję bardzo celowo ma tę [&]składnię. Każdy programista C ++ już kojarzy &znak z referencjami i wie o nieaktualnych referencjach.
MSalters
2
@MSalters Jaki masz sens? Sam narysowałem połączenie referencyjne. Nie powiedziałem, że lambda C ++ są wyjątkowo niebezpieczne, powiedziałem, że są tak samo niebezpieczne jak referencje. Nie twierdziłem, że lambd C ++ są złe, argumentowałem przeciwko twierdzeniu tej odpowiedzi (że C ++ dostało zamknięcia bardzo późno, ponieważ musieli wymyślić, jak zrobić to dobrze).
5
  1. SBMM sprawia, że ​​programy są bardziej deterministyczne (możesz dokładnie powiedzieć, kiedy obiekt jest niszczony);

Dla większości programistów system operacyjny jest niedeterministyczny, ich alokator pamięci jest niedeterministyczny, a większość pisanych programów jest współbieżna, a zatem z natury niedeterministyczna. Dodanie ograniczenia, że ​​destruktor jest wywoływany dokładnie na końcu zakresu, a nie nieco wcześniej lub nieco później, nie jest znaczącą praktyczną korzyścią dla zdecydowanej większości programistów.

  1. w językach korzystających z GC często trzeba ręcznie zarządzać zasobami (patrz na przykład zamykanie plików w Javie), co częściowo obala cel GC i jest również podatne na błędy;

Zobacz usingw C # i useF #.

  1. Pamięć sterty może również (bardzo elegancko, imo) być ograniczona zasięgiem (patrz std :: shared_ptr w C ++).

Innymi słowy, możesz wziąć stos, który jest rozwiązaniem ogólnego przeznaczenia i zmienić go tak, aby działał tylko w konkretnym przypadku, który jest poważnie ograniczający. To prawda, ale bezużyteczne.

Dlaczego SBMM nie jest szerzej stosowany? Jakie są jego wady?

SBMM ogranicza to, co możesz zrobić:

  1. SBMM stwarza problem z rosnącą liczbą leksyków najwyższej jakości, dlatego zamknięcia są popularne i łatwe w użyciu w językach takich jak C #, ale rzadkie i trudne w C ++. Zauważ, że istnieje ogólna tendencja do stosowania funkcjonalnych konstrukcji w programowaniu.

  2. SBMM wymaga destruktorów i utrudnia wywołania ogona, dodając więcej pracy do wykonania, zanim funkcja może wrócić. Wywołania tail są przydatne dla rozszerzalnych maszyn stanów i są dostarczane przez takie rzeczy jak .NET.

  3. Niektóre struktury danych i algorytmy są niezwykle trudne do wdrożenia przy użyciu SBMM. Zasadniczo wszędzie tam, gdzie cykle występują naturalnie. W szczególności algorytmy graficzne. Skutecznie kończysz pisanie własnego GC.

  4. Współbieżne programowanie jest trudniejsze, ponieważ przepływ sterowania, a zatem czasy życia obiektów są z natury niedeterministyczne. Praktycznymi rozwiązaniami w systemach przekazywania wiadomości są zwykle głębokie kopiowanie wiadomości i stosowanie nadmiernie długiego czasu życia.

  5. SBMM utrzymuje obiekty przy życiu aż do końca ich zakresu w kodzie źródłowym, który jest często dłuższy niż to konieczne i może być znacznie dłuższy niż to konieczne. Zwiększa to ilość unoszących się śmieci (nieosiągalne obiekty czekające na recykling). W przeciwieństwie do tego, śledzenie wyrzucania elementów bezużytecznych ma tendencję do uwalniania obiektów wkrótce po zniknięciu ostatniego odniesienia do nich, co może być znacznie wcześniej. Zobacz mity dotyczące zarządzania pamięcią: terminowość .

SBMM jest tak ograniczający, że programiści potrzebują drogi ucieczki w sytuacjach, w których nie można wprowadzić w życie okresów istnienia. W C ++ shared_ptroferuje drogę ucieczki, ale może być ~ 10 razy wolniejsza niż śledzenie śmieci . Więc użycie SBMM zamiast GC spowodowałoby, że większość ludzi źle się zachowuje przez większość czasu. Nie oznacza to jednak, że jest bezużyteczny. SBMM ma nadal wartość w kontekście systemów i programowania wbudowanego, w których zasoby są ograniczone.

FWIW możesz sprawdzić Fortha i Ady i poczytać o twórczości Nicolasa Wirtha.

Jon Harrop
źródło
1
Jeśli powiesz, które fragmenty mogę opracować lub zacytować artykuły.
Jon Harrop,
2
Jak istotne jest 10-krotnie wolniejsze działanie w kilku rzadkich przypadkach użycia niż wszechobecność we wszystkich przypadkach użycia? C ++ ma unikalny_ptr i dla większości celów jest wystarczający. Oprócz tego, zamiast atakować RAII przez C ++ (język, którego wielu lubi nienawidzić za to, że jest językiem archaicznym), jeśli zamierzasz atakować RAII poprzez atakowanie języka, spróbuj młodszego rodzeństwa z rodziny RAII, na przykład Rust. Rust w zasadzie naprawia wszystko, co C ++ się pomyliło, podczas gdy robi większość rzeczy, że C ++ też ma rację. Dalsze „używanie” daje ci bardzo ograniczony zestaw przypadków użycia i ignoruje kompozycję.
user1703394
2
„Jak istotne jest 10-krotnie wolniejsze działanie w kilku rzadkich przypadkach użycia niż wszechobecność we wszystkich przypadkach użycia?”. Po pierwsze, jest to okrągły argument: shared_ptrjest rzadki w C ++, ponieważ jest tak wolny. Po drugie, jest to porównanie jabłek i pomarańczy (jak już pokazałem cytowany artykuł), ponieważ shared_ptrjest wiele razy wolniejsze niż produkcja GC. Po trzecie, GC nie są wszechobecne i unika się ich w oprogramowaniu takim jak LMax i silnik FIX Rapid Addition.
Jon Harrop,
1
@Jon Harrop, jeśli nie oświeciłbyś mnie proszę. Jakiego magicznego przepisu używałeś przez ponad 30 lat, aby złagodzić przechodnie skutki używania głębokich zasobów? Bez takiego magicznego przepisu po ponad 30 latach mógłbym jedynie stwierdzić, że musiałeś błędnie przypisać, że zostałeś ugryziony przez inne przyczyny.
user1703394,
1
@Jon Harrop, shared_ptr nie jest rzadki, ponieważ jest powolny, jest rzadki, ponieważ w porządnie zaprojektowanym systemie potrzeba „współwłasności” jest rzadka.
user1703394 21.01.2015
4

Patrząc na jakiś indeks popularności, taki jak TIOBE (co jest oczywiście dyskusyjne, ale wydaje mi się, że twoje pytanie jest w porządku, aby z niego skorzystać), po pierwsze widzisz, że ~ 50% z 20 najlepszych to „języki skryptowe” lub „dialekty SQL” ”, gdzie„ łatwość użycia ”i środki abstrakcji mają znacznie większe znaczenie niż zachowanie deterministyczne. Z pozostałych „skompilowanych” języków jest około 50% języków z SBMM i ~ 50% bez. Więc biorąc pod uwagę języki skryptowe z obliczeń, powiedziałbym, że twoje założenie jest po prostu błędne, wśród skompilowanych języków te z SBMM są tak popularne jak te bez.

Doktor Brown
źródło
1
Czym różni się „łatwość użycia” od determinizmu? Czy język deterministyczny nie powinien być uważany za łatwiejszy w użyciu niż język niedeterministyczny?
Philipp
2
@Philipp Tylko rzeczy deterministyczne lub nieistotne. Czas życia obiektu nie ma znaczenia sam w sobie (chociaż C ++ i przyjaciele wiążą wiele rzeczy, które mają znaczenie, by sprzeciwić się czasowi życia, ponieważ mogą). Gdy nieosiągalny cel został uwolniony nie ma znaczenia, ponieważ, z definicji, nie jesteś go przy użyciu więcej.
I jeszcze jedno, różne „języki skryptowe”, takie jak Perl i Python, również wykorzystują liczenie referencji jako podstawowy sposób zarządzania pamięcią.
Philipp
1
@Philipp Przynajmniej w świecie Python jest to uważane za szczegół implementacji CPython, a nie właściwość języka (i praktycznie każda inna implementacja unika przeliczania). Ponadto argumentowałbym, że zliczanie referencji bez opcji rezygnacji z cyklu tworzenia kopii zapasowych GC nie kwalifikuje się jako SBMM lub RAII. W rzeczywistości trudno byłoby znaleźć zwolenników RAII, którzy uważają ten styl zarządzania pamięcią za porównywalny z RAII (głównie dlatego, że tak nie jest, cykle w dowolnym miejscu mogą zapobiec szybkiemu zwolnieniu w innym miejscu w programie).
3

Jedną z głównych zalet systemu GC, o którym nikt jeszcze nie wspomniał, jest to, że odniesienie w systemie GC gwarantuje zachowanie swojej tożsamości tak długo, jak istnieje . Jeśli ktoś wywoła IDisposable.Dispose(.NET) lub AutoCloseable.Close(Java) na obiekcie, podczas gdy istnieją kopie odwołania, kopie te będą nadal odnosić się do tego samego obiektu. Obiekt nie będzie już do niczego przydatny, ale próby jego użycia będą miały przewidywalne zachowanie kontrolowane przez sam obiekt. Natomiast w C ++, jeśli kod wywołuje deleteobiekt, a później próbuje go użyć, cały stan systemu staje się całkowicie niezdefiniowany.

Inną ważną rzeczą, na którą należy zwrócić uwagę, jest to, że zarządzanie pamięcią oparte na zakresie działa bardzo dobrze w przypadku obiektów o jasno zdefiniowanej własności. Działa znacznie gorzej, a czasem wręcz źle, z obiektami, które nie mają określonej własności. Ogólnie rzecz biorąc, zmienne obiekty powinny mieć właścicieli, podczas gdy niezmienne obiekty nie muszą tego robić, ale pojawia się zmarszczka: kod bardzo często używa instancji typów zmiennych do przechowywania niezmiennych danych, zapewniając, że żadne odniesienie nie będzie narażone na działanie kod, który może mutować instancję. W takim scenariuszu wystąpienia klasy mutowalnej mogą być współużytkowane przez wiele niezmiennych obiektów, a zatem nie mają wyraźnego prawa własności.

supercat
źródło
4
Właściwość, o której mowa w pierwszej połowie, to bezpieczeństwo pamięci. Chociaż GC jest bardzo łatwym sposobem na osiągnięcie bezpieczeństwa pamięci, GC nie jest konieczne: spójrz na Rust na dobrze wykonany przykład.
@delnan: Kiedy przeglądam rust-lang.org, moja przeglądarka nie wydaje się nawigować z tego miejsca; gdzie powinienem szukać więcej informacji? Mam wrażenie, że bezpieczeństwo pamięci bez GC nakłada pewne ograniczenia na struktury danych, które mogą nie pasować do wszystkich rzeczy, które aplikacja może potrzebować, ale byłbym szczęśliwy, gdyby udowodniono, że się mylę.
supercat,
1
Nie znam ani jednego (ani nawet niewielkiego zestawu) dobrych referencji w tym zakresie; moja wiedza na temat Rust zgromadziła ponad rok lub dwa czytanie listy mailingowej (i wszystkich rzeczy, do których linki znajdują się w mailach, w tym różne samouczki, posty na blogach poświęconych projektowaniu języka, problemy z githubem, ThisWeekInRust i inne). Aby krótko odnieść się do twojego wrażenia: Tak, każda bezpieczna konstrukcja (koniecznie) nakłada ograniczenia, ale dla praktycznie każdego bezpiecznego kodu pamięci, istnieje odpowiednia bezpieczna konstrukcja lub może być napisana. Zdecydowanie najpopularniejsze z nich istnieją już w języku i stdlib, wszystkie inne można zapisać w kodzie użytkownika.
@delnan: Czy Rust wymaga zablokowanych aktualizacji liczników referencyjnych, czy też ma jakieś inne sposoby radzenia sobie z obiektami niezmiennymi (lub obiektami niepodzielnie owiniętymi), które nie mają określonej własności? Czy Rust ma pojęcie zarówno wskaźników „posiadających obiekt”, jak i „nie posiadających”? Przypominam sobie artykuł na temat wskaźników „Xonor”, ​​który omawiał ideę obiektów posiadających jedno odniesienie, które „jest ich właścicielem”, oraz inne odniesienia, które nie są; gdy odniesienie „będące właścicielem” wykracza poza zakres, wszystkie odniesienia nieposiadające własności stają się odniesieniami do martwych obiektów i jako takie można je zidentyfikować ...
supercat
1
Nie sądzę, aby komentarze Stack Exchange były właściwym medium do oprowadzania po języku. Jeśli nadal jesteś zainteresowany, możesz przejść bezpośrednio do źródła (#rust IRC, lista mailingowa Dev-dev, itp.) I / lub uderzyć mnie w czat (powinieneś być w stanie go utworzyć).
-2

Po pierwsze, bardzo ważne jest, aby zdać sobie sprawę z tego, że zrównanie RAII z ​​SBMM. a nawet SBRM. Jedną z najbardziej istotnych (i najmniej znanych lub najbardziej niedocenianych) cech RAII jest fakt, że sprawia, że ​​„bycie zasobem” jest właściwością, która NIE jest przechodnia na kompozycję.

Poniższy post na blogu omawia ten ważny aspekt RAII i porównuje go do ulepszenia zasobów w językach GCed, które używają niedeterministycznego GC.

http://minorfs.wordpress.com/2011/04/29/why-garbage-collection-is-anti-productive/

Należy zauważyć, że podczas gdy RAII jest najczęściej używany w C ++, Python (w końcu wersja nie oparta na VM) ma destruktory i deterministyczny GC, który pozwala na użycie RAII razem z GC. Najlepszy z obu światów, gdyby tak było.

użytkownik1703394
źródło
1
-1 To jeden z najgorszych artykułów, jakie kiedykolwiek czytałem.
Jon Harrop,
1
Problem w językach nie polega na tym, że obsługują one GC, ale na tym, że porzucają RAII. Nie ma powodu, dla którego język / framework nie mógłby obsługiwać obu.
supercat
1
@Jon Harrop, czy mógłbyś opracować. Czy spośród oświadczeń zawartych w tym artykule jest nawet jedno z pierwszych 3 roszczeń, które nie mają zastosowania? Myślę, że możesz nie zgodzić się z twierdzeniem dotyczącym produktywności, ale pozostałe 3 twierdzenia są absolutnie ważne. Co najważniejsze, pierwsza dotyczy przechodniości bycia zasobem.
user1703394,
2
@ user1703394: Po pierwsze, cały artykuł jest oparty na słomkowym „języku GCed”, podczas gdy w rzeczywistości nie ma on nic wspólnego z odśmiecaniem. Po drugie, obwinia zbieranie śmieci, gdy w rzeczywistości błędy leżą w programowaniu obiektowym. Wreszcie jego argument jest spóźniony o 10 lat. Zdecydowana większość programistów już zmodernizowała języki, w których śmieci są gromadzone, właśnie dlatego, że oferują znacznie wyższą wydajność.
Jon Harrop,
1
Jego konkretne przykłady (pamięć RAM, otwarte uchwyty plików, blokady, wątki) są dość wymowne. Trudno mi przypomnieć sobie, kiedy ostatni raz musiałem pisać kod, który dotyczył bezpośrednio któregokolwiek z nich. Dzięki pamięci RAM GC automatyzuje wszystko. Za pomocą uchwytów plików piszę kod tak, jak File.ReadLines file |> Seq.lengthdla mnie abstrakcje obsługują zamykanie. Zamki i wątki zamieniłem na .NET Taski F # MailboxProcessor. Całe to „Eksplodowaliśmy ilość ręcznego zarządzania zasobami” to po prostu kompletna bzdura.
Jon Harrop,