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
- SBMM sprawia, że programy są bardziej deterministyczne (możesz dokładnie powiedzieć, kiedy obiekt jest niszczony);
- 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;
- Pamięć sterty może również (bardzo elegancko, imo) być ograniczona zasięgiem (patrz
std::shared_ptr
w C ++).
Dlaczego SBMM nie jest szerzej stosowany? Jakie są jego wady?
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.Odpowiedzi:
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
using
w C #,with
w Pythonie,try
-with-resources w najnowszych wersjach Java.źródło
using
instrukcje są możliwe tylko lokalnie. W ten sposób nie można oczyścić zasobów przechowywanych w zmiennych członkowskich.using
jest żartem w porównaniu do RAII, tak po prostu wiesz.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
który automatycznie wywołuje
.close()
zasób przy wyjściu z bloku. C # maIDisposable
interfejs, który pozwala.Dispose()
na wywołanie przy pozostawieniuusing (...) { ... }
instrukcji. Python mawith
instrukcję: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.
Myślę, że Node.js używa tej samej strategii.
źródło
with-open-filehandle
funkcje, które otwierają plik, dają funkcję i po powrocie funkcji ponownie zamykają plik.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.
źródło
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.
źródło
[&]
składnię. Każdy programista C ++ już kojarzy&
znak z referencjami i wie o nieaktualnych referencjach.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.
Zobacz
using
w C # iuse
F #.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.
SBMM ogranicza to, co możesz zrobić:
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.
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.
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.
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.
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_ptr
oferuje 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.
źródło
shared_ptr
jest 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_ptr
jest 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.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.
źródło
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) lubAutoCloseable.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łujedelete
obiekt, 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.
źródło
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.
źródło
File.ReadLines file |> Seq.length
dla mnie abstrakcje obsługują zamykanie. Zamki i wątki zamieniłem na .NETTask
i F #MailboxProcessor
. Całe to „Eksplodowaliśmy ilość ręcznego zarządzania zasobami” to po prostu kompletna bzdura.