IT박스

RAII 대 가비지 수집기

itboxs 2020. 10. 21. 07:48
반응형

RAII 대 가비지 수집기


저는 최근 CppCon 2016에서 Herb Sutter가 "Leak Free C ++ ..."에 대한 멋진 강연을 보았습니다. 그는 스마트 포인터를 사용하여 RAII (리소스 획득은 초기화)를 구현하는 방법에 대해 이야기하고 대부분의 메모리 누수 문제를 해결하는 방법에 대해 이야기했습니다.

이제 궁금합니다. 좋은 것처럼 보이는 RAII 규칙을 엄격하게 따르는 경우 C ++에서 가비지 수집기를 사용하는 것과 다른 이유는 무엇입니까? RAII를 사용하면 프로그래머가 리소스가 다시 해제되는시기를 완전히 제어 할 수 있다는 것을 알고 있습니다. 그러나 어떤 경우에도 가비지 수집기를 갖는 것이 유익합니까? 정말 덜 효율적일까요? 가비지 수집기를 사용하면 코드 전체에서 작은 메모리 조각을 해제하는 대신 한 번에 더 큰 메모리 청크를 해제 할 수 있으므로 더 효율적일 수 있다고 들었습니다.


좋은 것처럼 보이는 RAII 규칙을 엄격하게 따르는 경우 C ++에서 가비지 수집기를 사용하는 것과 다른 이유는 무엇입니까?

둘 다 할당을 처리하지만 완전히 다른 방식으로 처리합니다. Java의 GC와 같은 GC를 사용하는 경우 자체 오버 헤드를 추가하고 리소스 릴리스 프로세스에서 결정 성을 일부 제거하고 순환 참조를 처리합니다.

하지만 성능 특성이 훨씬 다른 특정 경우에 GC를 구현할 수 있습니다. 고성능 / 고 처리량 서버에서 소켓 연결을 닫기 위해 한 번 구현했습니다 (소켓 닫기 API를 호출하는 데 너무 오래 걸리고 처리량 성능이 저하됨). 여기에는 메모리가 없지만 네트워크 연결과 순환 종속성 처리가 포함되지 않았습니다.

RAII를 사용하면 프로그래머가 리소스가 다시 해제되는시기를 완전히 제어 할 수 있다는 것을 알고 있습니다. 그러나 어떤 경우에도 가비지 수집기를 갖는 것이 유익합니까?

이 결정론은 GC가 허용하지 않는 기능입니다. 때로는 어떤 시점 이후에 정리 작업 (임시 파일 삭제, 네트워크 연결 종료 등)이 수행되었음을 알 수 있기를 원합니다 .

이러한 경우 GC는이를 자르지 않으므로 C #의 이유 (예 : IDisposable인터페이스 )가 있습니다.

가비지 수집기를 사용하면 코드 전체에서 작은 메모리 조각을 해제하는 대신 한 번에 더 큰 메모리 청크를 해제 할 수 있으므로 더 효율적일 수 있다고 들었습니다.

구현에 따라 달라질 수 있습니다.


가비지 콜렉션은 RAII가 해결할 수없는 특정 클래스의 자원 문제를 해결합니다. 기본적으로 순환 종속성으로 귀결됩니다.

이것은 두 가지 이점을 제공합니다. 첫째, RAII가 해결할 수없는 특정 유형의 문제가있을 것입니다. 내 경험상 드물다.

더 큰 하나는 프로그래머가 게으른하고 할 수 있다는 것입니다 걱정하지 당신이 지연 정리를 신경 쓰지 않는 메모리 자원 수명 및 특정 기타 리소스에 대해. 특정 종류의 문제에 대해 신경 쓸 필요가 없을 때 다른 문제에 대해 신경을 쓸 수 있습니다. 이를 통해 집중하려는 문제 부분에 집중할 수 있습니다.

단점은 RAII가 없으면 수명을 제한하려는 리소스를 관리하는 것이 어렵다는 것입니다. GC 언어는 기본적으로 매우 간단한 범위 제한 수명을 갖거나 C에서와 같이 수동으로 리소스를 사용했음을 알리는 리소스 관리를 수동으로 수행해야합니다. 그들의 개체 수명 시스템은 GC에 강하게 연결되어 있으며 대규모 복잡한 (아직주기가없는) 시스템의 엄격한 수명 관리에 적합하지 않습니다.

공정하게 말하면 C ++의 리소스 관리는 이렇게 크고 복잡한 (아직주기가없는) 시스템에서 제대로 수행하려면 많은 작업이 필요합니다. C # 및 유사한 언어는 쉬운 경우를 쉽게 만드는 대가로 터치를 더 어렵게 만듭니다.

대부분의 GC 구현은 또한 비 지역성 완전한 클래스를 강제합니다. 일반 객체의 연속 버퍼를 생성하거나 일반 객체를 하나의 더 큰 객체로 구성하는 것은 대부분의 GC 구현이 쉽게 만드는 일이 아닙니다. 반면에 C # struct에서는 다소 제한된 기능으로 값 형식을 만들 수 있습니다 . 현재의 CPU 아키텍처 시대에는 캐시 친 화성이 핵심이며 지역성 GC 힘의 부족은 큰 부담입니다. 이러한 언어에는 대부분 바이트 코드 런타임이 있으므로 이론적으로 JIT 환경은 일반적으로 사용되는 데이터를 함께 이동할 수 있지만 C ++에 비해 빈번한 캐시 누락으로 인해 균일 한 성능 손실을 얻는 경우가 더 많습니다.

GC의 마지막 문제는 할당 해제가 불확실하고 때때로 성능 문제를 일으킬 수 있다는 것입니다. 최신 GC는이를 과거보다 덜 문제로 만듭니다.


공지 사항 것을 RAII는 동안, 프로그래밍 관용구이다 GC는 메모리 관리 기술이다. 그래서 우리는 사과와 오렌지를 비교하고 있습니다.

그러나 RAII를 메모리 관리 측면으로 제한 하고 GC 기술과 비교할 수 있습니다.

소위 RAII 기반 메모리 관리 기술 ( 최소한 메모리 자원을 고려하고 파일과 같은 다른 것을 무시할 때 실제로 참조 계수를 의미 함 )과 진정한 가비지 수집 기술 의 주요 차이점 순환 참조 ( 순환 그래프의 경우 ) 처리입니다. .

참조 카운팅을 사용하려면 ( 약한 참조 또는 기타 항목을 사용하여) 특별히 코딩해야합니다 .

많은 유용한 경우 std::vector<std::map<std::string,int>>에서 참조 카운팅은 암시 적이며 (0 또는 1 일 수 있기 때문에) 실제로 생략되지만 생성자 및 소멸자 함수 (RAII에 필수)는 참조 카운팅 비트가있는 것처럼 작동합니다. 거의 없습니다). 에서는 std::shared_ptr진짜 기준 카운터가있다. 그러나 메모리는 여전히 암시 수동으로 관리 (과 newdelete생성자와 소멸자 내부 트리거),하지만 "암시" delete(소멸자에서) 자동 메모리 관리의 환상을 제공합니다. 그러나 호출 new하고 delete아직 일이 (그리고 그들은 시간 비용).

BTW GC 구현 은 특정 방식으로 순환 성을 처리 할 수 ​​있지만 (종종 그렇게합니다) GC에 그 부담을 남겨 둡니다 (예 : Cheney의 알고리즘 에 대해 읽음 ).

일부 GC 알고리즘 (특히 세대 별 복사 가비지 수집기)은 개별 개체에 대한 메모리를 해제하지 않고 복사 후 일괄 해제 됩니다. 실제로 Ocaml GC (또는 SBCL 하나)는 정품 C ++ RAII 프로그래밍 스타일보다 빠를 수 있습니다 ( 모든 종류가 아닌 일부 알고리즘의 경우).

일부 GC는 종료 (대부분 파일과 같은 비 메모리 외부 리소스 를 관리하는 데 사용됨)를 제공 하지만 거의 사용하지 않습니다 (대부분의 값은 메모리 리소스 만 사용하기 때문에). 단점은 최종화가 시간 보장을 제공하지 않는다는 것입니다. 실제로 마무리를 사용하는 프로그램은이를 최후의 수단으로 사용하고 있습니다 (예 : 파일 닫기는 마무리 외부에서 또는 파일과 함께 다소 명시 적으로 발생해야합니다).

예를 들어 값이 일부 변수 또는 일부 필드에 유지되지만 앞으로는 사용되지 않을 때와 같이 GC (및 RAII에서도 적어도 부적절하게 사용되는 경우)로 메모리 누수가 발생할 수 있습니다. 그들은 덜 자주 발생합니다.

쓰레기 수거 핸드북을 읽는 것이 좋습니다 .

C ++ 코드에서 Boehm의 GC 또는 Ravenbrook의 MPS를 사용 하거나 자체 추적 가비지 수집기를 코딩 할 수 있습니다 . 물론 GC를 사용하는 것은 트레이드 오프입니다 (비결 정성, 타이밍 보장 부족 등과 같은 약간의 불편 함이 있습니다.).

저는 RAII가 모든 경우에있어서 기억을 다루는 궁극적 인 방법이라고 생각하지 않습니다. 여러 경우에 C ++ 17에서 멋진 RAII 스타일로 코딩하는 것보다 진정하고 효율적인 GC 구현 (Ocaml 또는 SBCL 생각)으로 프로그램을 코딩하는 것이 더 간단하고 (개발하기) 더 빠를 수 있습니다 (실행하기). 다른 경우에는 그렇지 않습니다. YMMV.

예를 들어, 가장 멋진 RAII 스타일로 C ++ 17에서 Scheme 인터프리터를 코딩하는 경우에도 내부에 명시적인 GC 를 코딩 (또는 사용)해야 합니다 (Scheme 힙에 순환 성이 있기 때문). 그리고 대부분의 증명 도우미 는 GC 언어로 코딩되며, 종종 기능적인 언어로 코딩됩니다 (C ++로 코딩 된 유일한 것은 Lean ).

BTW, 나는 그러한 Scheme의 C ++ 17 구현을 찾는 데 관심이 있습니다 (하지만 직접 코딩하는 데는 관심이 적습니다).


RAII와 GC는 완전히 다른 방향으로 문제를 해결합니다. 어떤 사람들이 말하는 것에도 불구하고 그들은 완전히 다릅니다.

둘 다 리소스 관리가 어렵다는 문제를 해결합니다. 가비지 컬렉션은 개발자가 리소스 관리에 많은주의를 기울일 필요가 없도록 만들어 해결합니다. RAII는 개발자가 리소스 관리에 더 쉽게주의를 기울일 수 있도록하여이를 해결합니다. 그들이 똑같은 일을한다고 말하는 사람은 당신을 팔 것입니다.

언어의 최근 동향을 살펴보면 두 가지 접근 방식이 동일한 언어로 사용되는 것을 볼 수 있습니다. 솔직히 말해서 퍼즐의 양면이 정말 필요하기 때문입니다. 대부분의 객체에주의를 기울일 필요가 없도록 가비지 컬렉션을 사용하는 많은 언어를 볼 수 있으며, 이러한 언어는 with실제로주의를 기울이고 싶은 시간에 대해 RAII 솔루션 (예 : Python 연산자)을 제공합니다. 그들.

  • C ++는 생성자 / 소멸자를 통해 RAII를 제공하고 GC를 통해 shared_ptr제공합니다.
  • Python은 참조 계산 with시스템과 가비지 수집기를 통해 RAII 및 GC를 제공합니다.
  • 를 통해 C # 제공의 RAII IDisposableusing및 GC 세대 가비지 컬렉터를 통해

패턴은 모든 언어에서 잘립니다.


가비지 수집기의 문제 중 하나는 프로그램 성능을 예측하기 어렵다는 것입니다.

RAII를 사용하면 정확한 시간에 리소스가 범위를 벗어날 것이고 일부 메모리를 지우고 시간이 걸릴 것임을 알고 있습니다. 그러나 가비지 수집기 설정의 마스터가 아니라면 정리가 언제 발생할지 예측할 수 없습니다.

예를 들어, GC를 사용하면 큰 청크를 해제 할 수 있기 때문에 작은 개체 무리를 더 효과적으로 청소할 수 있지만 빠른 작업이 아니며 언제 발생할지 예측하기 어렵고 "대형 청크 정리"로 인해 프로세서 시간이 약간 걸리고 프로그램 성능에 영향을 미칠 수 있습니다.


대략적으로 말하면. RAII 관용구는 대기 시간지터에 더 좋을 수 있습니다 . 가비지 수집기는 시스템의 처리량에 더 적합 할 수 있습니다 .


"Efficient" is a very broad term, in sense of development efforts RAII is typically less efficient than GC, but in terms of performance GC is typically less efficient than RAII. However it is possible to provide contr-examples for both cases. Dealing with generic GC when you have very clear resource (de)allocation patters in managed languages can be rather troublesome, just like the code using RAII can be surprisingly inefficient when shared_ptr is used for everything for no reason.


Garbage collection and RAII each support one common construct for which the other is not really suitable.

In a garbage-collected system, code may efficiently treat references to immutable objects (such as strings) as proxies for the data contained therein; passing around such references is almost as cheap as passing around "dumb" pointers, and is faster than making a separate copy of the data for each owner, or trying to track ownership of a shared copy of the data. In addition, garbage-collected systems make it easy to create immutable object types by writing a class which creates a mutable object, populating it as desired, and providing accessor methods, all while refraining from leaking references to anything that might mutate it once the constructor finishes. In cases where references to immutable objects need to be widely copied but the objects themselves don't, GC beats RAII hands down.

On the other hand, RAII is excellent at handling situations where an object needs to acquire exclusive services from outside entities. While many GC systems allow objects to define "Finalize" methods and request notification when they are found to be abandoned, and such methods may sometimes manage to release outside services that are no longer needed, they are seldom reliable enough to provide a satisfactory way of ensuring timely release of outside services. For management of non-fungible outside resources, RAII beats GC hands down.

The key difference between the cases where GC wins versus those where RAII wins is that GC is good at managing fungible memory that can be freed on an as-needed basis, but poor at handling non-fungible resources. RAII is good at handling objects with clear ownership, but bad at handling ownerless immutable data holders which have no real identity apart from the data they contain.

Because neither GC nor RAII handles all scenarios well, it would be helpful for languages to provide good support for both of them. Unfortunately, languages which focus on one tend to treat the other as an afterthought.


The main part of the question about whether one or the other is "beneficial" or more "efficient" cannot be answered without giving lots of context and arguing about the definitions of these terms.

Beyond that, you can basically feel the tension of the ancient "Is Java or C++ the better language?" flamewar crackling in the comments. I wonder what an "acceptable" answer to this question could look like, and am curious to see it eventually.

But one point about a possibly important conceptual difference has not yet been pointed out: With RAII, you are tied to the thread that calls the destructor. If your application is single threaded (and even though it was Herb Sutter who stated that The Free Lunch Is Over: Most software today effectively still is single-threaded), then a single core may be busy with handling the cleanups of objects that are no longer relevant for the actual program...

In contrast to that, the garbage collector usually runs in its own thread, or even multiple threads, and is thus (to some extent) decoupled from the execution of the other parts.

(Note: Some answers already tried to point out application patterns with different characteristics, mentioned efficiency, performance, latency and throughput - but this specific point was not mentioned yet)


RAII uniformly deals with anything that is describable as a resource. Dynamic allocations are one such resource, but they are by no means the only one, and arguably not the most important one. Files, sockets, database connections, gui feedback and more are all things that can be managed deterministically with RAII.

GCs only deal with dynamic allocations, relieving the programmer of worrying about the total volume of allocated objects over the lifetime of the program (they only have to care about the peak concurrent allocation volume fitting)


RAII and garbage collection are intended to solve different problems.

When you use RAII you leave an object on the stack which sole purpose is to clean up whatever it is you want managed (sockets, memory, files, etc.) on leaving the scope of the method. This is for exception-safety, not just garbage collection, which is why you get responses about closing sockets and freeing mutexes and the like. (Okay, so no one mentioned mutexes besides me.) If an exception is thrown, stack-unwinding naturally cleans up the resources used by a method.

Garbage collection is the programmatic management of memory, though you could "garbage-collect" other scarce resources if you'd like. Explicitly freeing them makes more sense 99% of the time. The only reason to use RAII for something like a file or socket is you expect the use of the resource to be complete when the method returns.

Garbage collection also deals with objects that are heap-allocated, when for instance a factory constructs an instance of an object and returns it. Having persistent objects in situations where control must leave a scope is what makes garbage collection attractive. But you could use RAII in the factory so if an exception is thrown before you return, you don't leak resources.


I even heard that having a garbage collector can be more efficient, as it can free larger chunks of memory at a time instead of freeing small memory pieces all over the code.

That's perfectly doable - and, in fact, is actually done - with RAII (or with plain malloc/free). You see, you don't necessarily always use the default allocator, which deallocates piecemeal only. In certain contexts you use custom allocators with different kinds of functionality. Some allocators have the in-built ability of freeing everything in some allocator region, all at once, without having to iterate individual allocated elements.

Of course, you then get into the question of when to deallocate everything - whether the use of those allocators (or the slab of memory with which they're associated has to be RAIIed or not, and how.

참고URL : https://stackoverflow.com/questions/44325085/raii-vs-garbage-collector

반응형