요즘에는 많은 언어가 가비지 수집됩니다. 타사에서 C ++로도 사용할 수 있습니다. 그러나 C ++에는 RAII 및 스마트 포인터가 있습니다. 가비지 수집을 사용하는 요점은 무엇입니까? 여분의 일을하고 있습니까?
그리고 C #과 같은 다른 언어에서 모든 참조가 사양 및 구현에 의해 스마트 포인터 (RAII를 따로 유지)로 취급된다면 가비지 수집기가 여전히 필요합니까? 아니라면 왜 그렇지 않습니까?
요즘에는 많은 언어가 가비지 수집됩니다. 타사에서 C ++로도 사용할 수 있습니다. 그러나 C ++에는 RAII 및 스마트 포인터가 있습니다. 가비지 수집을 사용하는 요점은 무엇입니까? 여분의 일을하고 있습니까?
그리고 C #과 같은 다른 언어에서 모든 참조가 사양 및 구현에 의해 스마트 포인터 (RAII를 따로 유지)로 취급된다면 가비지 수집기가 여전히 필요합니까? 아니라면 왜 그렇지 않습니까?
답변:
가비지 수집을 사용하는 이유는 무엇입니까?
나는 당신이 참조 카운트 된 스마트 포인터를 의미한다고 가정하고 그것들은 (가상적인) 가비지 수집의 형태라는 것을 주목할 것입니다. "나는 참조 카운트 된 스마트 포인터보다 다른 형태의 가비지 콜렉션의 장점은 무엇입니까?" 대신에.
정확도 . 참조 카운트만으로도 사이클이 누출되므로 참조 카운트 스마트 포인터는 다른 기술을 추가하여 사이클을 포착하지 않는 한 일반적으로 메모리를 누설합니다. 이러한 기술이 추가되면 참조 횟수의 단순성 이점이 사라졌습니다. 또한 범위 기반 참조 계산 및 추적 GC는 서로 다른 시간에 값을 수집하며, 때로는 참조 계산이 더 일찍 수집하고 추적 GC가 더 일찍 수집하는 경우도 있습니다.
처리량 . 스마트 포인터는 특히 참조 카운트가 원자 적으로 충돌 할 때 다중 스레드 응용 프로그램과 관련하여 가장 효율적인 가비지 수집 형식 중 하나입니다. 이를 완화하기 위해 설계된 고급 참조 카운팅 기술이 있지만 프로덕션 환경에서 추적 GC는 여전히 선택 알고리즘입니다.
잠복 . 일반적인 스마트 포인터 구현으로 소멸자가 눈에 띄게되어 무한한 일시 정지 시간이 발생합니다. 가비지 수집의 다른 형태는 훨씬 더 점진적이며 심지어 실시간 일 수도 있습니다 (예 : Baker 's treadmill).
아무도이 각도에서 그것을 보지 않았기 때문에 나는 당신의 질문을 다시 말할 것입니다 : 도서관에서 그것을 할 수 있다면 왜 언어에 무언가를 넣습니까? 특정 구현 및 구문 세부 사항을 무시하고 GC / 스마트 포인터는 기본적으로 해당 질문의 특별한 경우입니다. 라이브러리에서 가비지 컬렉터를 언어로 구현할 수있는 이유는 무엇입니까?
그 질문에 대한 몇 가지 답변이 있습니다. 가장 중요한 첫 번째 :
모든 코드가이 코드를 사용하여 상호 운용 할 수 있는지 확인하십시오. 이것이 Java / C # / Python / Ruby까지 코드 재사용과 코드 공유가 실제로 시작되지 않은 큰 이유 라고 생각 합니다 . 라이브러리는 통신이 필요하며, 신뢰할 수있는 유일한 공유 언어는 언어 사양 자체 (그리고 표준 라이브러리)에있는 것입니다. C ++에서 라이브러리를 재사용하려고 시도했다면 표준 메모리 의미론으로 인한 끔찍한 고통을 경험했을 것입니다. 구조체를 일부 lib에 전달하고 싶습니다. 참조를 전달합니까? 바늘? scoped_ptr
?smart_ptr
? 소유권을 전달하고 있습니까? 그것을 나타내는 방법이 있습니까? lib가 할당해야하는 경우 어떻게해야합니까? 할당자를 제공해야합니까? 메모리 관리를 언어의 일부로 만들지 않기 때문에 C ++은 각 라이브러리 쌍이 여기에서 자신의 특정 전략을 협상하도록 강요하므로 모든 사람들이 동의하는 것은 정말 어렵습니다. GC는이를 완전한 비 발행으로 만듭니다.
주위의 구문을 디자인 할 수 있습니다. C ++은 메모리 관리 자체를 캡슐화하지 않기 때문에 사용자 수준 코드가 모든 세부 사항을 표현할 수 있도록 다양한 구문 후크를 제공해야합니다. 포인터, 참조, const
참조 연산자, 간접 연산자, 주소 등이 있습니다. 메모리 관리를 언어 자체로 롤링하면 그 주위에 구문을 설계 할 수 있습니다. 이러한 연산자는 모두 사라지고 언어가 더 깨끗하고 단순 해집니다.
높은 투자 수익을 얻습니다. 주어진 코드 조각이 생성하는 가치는 그것을 사용하는 사람들의 수로 곱해집니다. 이는 사용자가 많을수록 소프트웨어에 더 많은 비용을 투자 할 수 있음을 의미합니다. 기능을 언어로 이동하면 언어의 모든 사용자가 해당 기능을 사용하게됩니다. 즉, 해당 사용자의 하위 집합에서만 사용하는 라이브러리보다 더 많은 노력을 할당 할 수 있습니다. 그렇기 때문에 Java 및 C #과 같은 언어는 절대적으로 최고의 VM과 환상적인 가비지 수집기를 보유하고 있습니다. 개발 비용은 수백만 명의 사용자에게 상각됩니다.
Dispose
비트 맵을 캡슐화하는 객체를 호출 하면 해당 객체에 대한 참조는 배치 된 비트 맵 객체에 대한 참조가됩니다. 다른 코드에서 계속 사용할 것으로 예상되는 동안 객체가 조기에 삭제 된 경우 비트 맵 클래스는 다른 코드가 예측 가능한 방식으로 실패 할 수 있습니다. 반대로 해제 된 메모리에 대한 참조를 사용하는 것은 정의되지 않은 동작입니다.
가비지 콜렉션은 기본적으로 할당 된 오브젝트가 더 이상 도달 할 수없는 시점에서 자동으로 해제됨을 의미합니다.
보다 정확하게 말하면, 순환 참조 객체는 절대로 해제되지 않기 때문에 프로그램에 도달 할 수 없을 때 해제됩니다.
스마트 포인터 는 일반 포인터처럼 동작 하지만 추가 기능이 추가 된 모든 구조를 나타 냅니다. 여기 에는 할당 취소뿐만 아니라 쓰기 중 복사, 바운드 검사 등이 포함됩니다.
지금까지 언급했듯이 스마트 포인터 를 사용 하여 가비지 수집 형식을 구현할 수 있습니다 .
그러나 생각의 기차는 다음과 같은 방식으로 진행됩니다.
물론 처음부터 이와 같이 디자인 할 수 있습니다. C #은 가비지 수집 되도록 설계되었으므로new
개체와 참조가 범위를 벗어나면 해제됩니다. 이 작업을 수행하는 방법은 컴파일러에 달려 있습니다.
그러나 C ++에서는 가비지 수집이 의도되지 않았습니다. 포인터를 할당하고 int* p = new int;
범위를 p
벗어나면 스택에서 자체적으로 제거되지만 아무도 할당 된 메모리를 처리하지 않습니다.
이제 시작부터 유일한 것은 결정 론적 소멸자 입니다. 오브젝트가 작성된 범위를 벗어나면 소멸자가 호출됩니다. 템플릿 및 연산자 오버로드와 함께 포인터처럼 동작하지만 소멸자 기능을 사용하여 첨부 된 리소스를 정리하는 래퍼 객체를 디자인 할 수 있습니다 (RAII). 이것을 스마트 포인터 라고 부릅니다 .
이것은 모두 C ++에만 해당됩니다. 연산자 오버로드, 템플릿, 소멸자 ...이 특정 언어 상황에서는 원하는 GC를 제공하는 스마트 포인터를 개발했습니다.
그러나 처음부터 GC를 사용하여 언어를 디자인하는 경우 이는 구현 세부 사항 일뿐입니다. 방금 객체가 정리되고 컴파일러 가이 작업을 수행한다고 말합니다.
C ++과 같은 스마트 포인터는 C #과 같은 언어에서는 가능하지 않을 것입니다 .C #과 같은 언어에서는 전혀 결정적인 파괴가 없습니다 (C #은 .Dispose()
특정 객체 를 호출하기 위해 구문 설탕을 제공 하여이 문제를 해결 합니다). 참조되지 않은 리소스는 GC에 의해 최종적 으로 재생되지만 정확히 발생할 때 정의되지 않습니다.
그리고 이것은 GC가보다 효율적으로 작업 할 수있게합니다. .NET GC는 스마트 포인터보다 언어에 더 깊숙이 내장되어 있기 때문에 메모리 작동을 지연시키고 블록 단위로 수행하여 더 저렴하게 만들거나 객체를 얼마나 자주 사용하는지에 따라 효율성을 높이기 위해 메모리를 움직일 수 있습니다. 액세스됩니다.
IDisposable
와 using
. 그러나 약간의 프로그래머 노력이 필요하기 때문에 데이터베이스 연결 핸들과 같은 매우 부족한 리소스에만 일반적으로 사용됩니다.
IDisposable
단지 기존의 대체하여 구문 let ident = value
으로 use ident = value
...
using
가비지 수집과 전혀 관련이 없으며 C ++의 소멸자와 같이 변수가 범위를 벗어나면 함수를 호출합니다.
메모리 관리에 사용되는 가비지 수집과 스마트 포인터 사이에는 두 가지 큰 차이점이 있습니다.
전자는 스마트 포인터가 가지지 않는 GC가 가비지를 수집한다는 것을 의미합니다. 스마트 포인터를 사용하는 경우 이런 종류의 쓰레기를 만들지 말고 수동으로 처리 할 준비를해야합니다.
후자는 스마트 포인터가 아무리 스마트해도 작동이 프로그램의 작업 스레드를 느리게한다는 것을 의미합니다. 가비지 콜렉션은 작업을 지연시키고 다른 스레드로 이동할 수 있습니다. 이를 통해 전반적으로 더 효율적이 될 수 있습니다 (실제로 현대 GC의 런타임 비용은 스마트 포인터의 추가 오버 헤드가 없어도 정상적인 malloc / free 시스템보다 적습니다). 응용 프로그램 스레드의 방법.
이제 프로그래밍 방식의 구성 요소 인 스마트 포인터는 가비지 수집의 범위를 완전히 벗어난 다른 흥미로운 모든 종류의 작업을 수행하는 데 사용할 수 있습니다 (Dario의 답변 참조). 그렇게하려면 스마트 포인터가 필요합니다.
그러나 메모리 관리를 위해 가비지 수집을 대체하는 스마트 포인터의 전망은 보이지 않습니다. 그들은 단순히 그것에 능숙하지 않습니다.
using
. 또한 GC의 비 결정적 동작은 실시간 시스템에서 금지 될 수 있습니다 (GC가 사용되지 않는 이유). 또한 GC가 너무 복잡하여 메모리가 실제로 누출되어 매우 비효율적이라는 사실을 잊지 마십시오 (예 : Boehm…).
가비지 수집이라는 용어는 수집 할 가비지가 있음을 의미합니다. C ++에서 스마트 포인터는 여러 가지 특징, 특히 가장 중요한 unique_ptr로 제공됩니다. unique_ptr은 기본적으로 단일 소유권 및 범위 지정 구문입니다. 잘 설계된 코드에서 대부분의 힙 할당 항목은 unique_ptr 스마트 포인터 뒤에 있으며 이러한 자원의 소유권은 항상 잘 정의됩니다. unique_ptr에는 오버 헤드가 거의 없으며 unique_ptr은 전통적으로 사람들을 관리되는 언어로 이끌었던 수동 메모리 관리 문제를 대부분 제거합니다. 더 많은 코어가 동시에 실행되면서 일반화되고 있으므로 특정 시점에 고유하고 잘 정의 된 소유권을 사용하도록 코드를 구동하는 디자인 원칙이 성능에있어 더욱 중요해졌습니다.
잘 설계된 프로그램, 특히 다중 스레드 환경에서도 공유 데이터 구조없이 모든 것을 표현할 수있는 것은 아니며 스레드가 실제로 필요한 데이터 구조의 경우 스레드가 통신해야합니다. c ++의 RAII는 단일 스레드 설정의 수명 문제에 대해 잘 작동합니다. 다중 스레드 설정의 경우 개체 수명이 계층 적으로 완전히 정의되지 않을 수 있습니다. 이러한 상황에서 shared_ptr을 사용하면 솔루션의 상당 부분을 제공합니다. 리소스의 공유 소유권을 만들면 C ++에서 이것이 가비지로 볼 수있는 유일한 곳이지만, 소량으로 적절한 디자인의 c ++ 프로그램을 본격적인 가비지 수집보다 shared-ptr로 '리터'수집을 구현하는 데 더 고려해야합니다. 다른 언어로 구현됩니다. C ++에는 '쓰레기'가 많지 않습니다.
다른 사람들이 언급했듯이 참조 횟수 스마트 포인터는 가비지 수집의 한 형태이며 하나의 주요 문제가 있습니다. 대부분 참조 카운트 된 가비지 수집 양식의 단점으로 사용되는 예는 서로에 대한 스마트 포인터로 연결된 고립 된 데이터 구조를 작성하여 서로 수집되지 않도록하는 오브젝트 클러스터를 작성하는 데 문제가 있습니다. 액터 모델 계산에 따라 설계된 프로그램에서 데이터 구조는 일반적으로 대규모 스레드에서 주로 사용되는 것처럼 다중 스레드 프로그래밍에 광범위한 공유 데이터 접근 방식을 사용할 때 이러한 수집 불가능 클러스터가 C ++에서 발생하는 것을 허용하지 않습니다. 업계에서 이러한 고아 클러스터는 빠르게 현실이 될 수 있습니다.
공유 포인터 사용법에 의해 다중 스레드 프로그래밍을위한 액터 모델의 계산 접근법과 shared_ptr의 제한된 사용과 결합 된 unique_ptr의 광범위한 사용을 의미한다면, 다른 형태의 가비지 콜렉션은 당신을 사지 않습니다. 추가 혜택. 그러나 모든 것을 공유하는 접근 방식으로 인해 모든 곳에서 shared_ptr을 사용하게되면 동시성 모델을 전환하거나 더 넓은 소유권 공유 및 데이터 구조에 대한 동시 액세스에보다 적합한 관리되는 언어로 전환하는 것을 고려해야합니다.
Rust
가비지 수집이 필요하지 않다는 것을 의미합니까 ?
대부분의 스마트 포인터는 참조 횟수를 사용하여 구현됩니다. 즉, 객체를 참조하는 각 스마트 포인터는 객체 참조 카운트를 증가시킵니다. 그 수가 0이되면 개체가 해제됩니다.
순환 참조가 있으면 문제가 있습니다. 즉, A에는 B에 대한 참조가 있고 B에는 C에 대한 참조가 있고 C에는 A에 대한 참조가 있습니다. 스마트 포인터를 사용하는 경우 A, B 및 C와 관련된 메모리를 해제하려면 수동으로해야합니다. weak_ptr
C ++에서 사용하는 순환 참조를 "중단"하십시오 .
가비지 콜렉션 (일반적으로)은 상당히 다르게 작동합니다. 요즘 대부분의 가비지 수집기는 도달 가능성 테스트를 사용합니다 . 즉, 모든 그 참조를 참조하는 것이 객체 추적, 물체 다음 스택에 참조 세계적으로 액세스 할 수있는 사람을 모두 살펴보고있다 가 , 다른 등 모든 쓰레기가 있습니다 참조하십시오.
이런 식으로 순환 참조는 더 이상 중요하지 않습니다. A, B 및 C에 도달 할 수 없으면 메모리를 회수 할 수 있습니다.
"실제"가비지 콜렉션에는 다른 장점이 있습니다. 예를 들어, 메모리 할당은 매우 저렴합니다. 메모리 블록의 "끝"에 대한 포인터를 늘리면됩니다. 할당 해제에는 상각 된 상각 비용도 있습니다. 물론 C ++과 같은 언어를 사용하면 원하는 방식으로 메모리 관리를 거의 구현할 수 있으므로 더 빠른 할당 전략을 수립 할 수 있습니다.
물론 C ++에서 힙 할당 메모리의 양은 일반적으로 C # /. NET과 같이 참조가 많은 언어보다 적습니다. 그러나 그것은 실제로 가비지 수집 대 스마트 포인터 문제가 아닙니다.
어쨌든 문제는 잘랐으며 건조하지 않는 것입니다. 그들은 각각 장단점이 있습니다.
성능 에 관한 것 입니다. 메모리를 할당 해제하려면 많은 관리가 필요합니다. 할당이 백그라운드에서 실행되면 포 그라운드 프로세스의 성능이 향상됩니다. 불행히도, 메모리 할당은 게으르지 않으며 (할당 된 객체는 다음 순간에 사용될 것입니다), 릴리즈하는 객체는 가능합니다.
C ++에서 (GC없이) 많은 객체를 할당하고 "hello"를 인쇄 한 다음 삭제하십시오. 객체를 해제하는 데 시간이 얼마나 걸리는지 놀랄 것입니다.
또한 GNU libc는 메모리 할당 해제를위한보다 효과적인 도구를 제공합니다 ( obstacks 참조) . 나는 obstacks에 대한 경험이 없으며 결코 사용하지 않았다는 것을 알아야합니다.
가비지 콜렉션은보다 효율적일 수 있습니다. 기본적으로 메모리 관리 오버 헤드를 '배치'하여 한 번에 수행합니다. 일반적으로 이렇게하면 메모리 할당 해제시 전체 CPU 소비가 줄어들지 만 어느 시점에서 할당 해제 활동이 크게 늘어납니다. GC가 올바르게 설계되지 않은 경우 GC가 메모리를 할당 해제하려고 시도하는 동안 사용자에게 '일시 중지'로 표시 될 수 있습니다. 대부분의 최신 GC는 가장 불리한 조건을 제외하고는 사용자에게 보이지 않게하는 데 매우 능숙합니다.
스마트 포인터 (또는 모든 참조 계산 체계)는 코드를 볼 때 예상 할 때 정확하게 발생한다는 이점이 있습니다 (스마트 포인터가 범위를 벗어나면 물건이 삭제됩니다). 당신은 여기저기서 할당 해제가 거의 일어나지 않습니다. 전체적으로 할당 해제에 더 많은 CPU 시간을 사용할 수 있지만 프로그램에서 발생하는 모든 일에 분산되어 있기 때문에 (일부 몬스터 데이터 구조의 할당 해제를 금지) 사용자에게 표시 될 가능성은 적습니다.
응답 성이 중요한 작업을 수행하는 경우 스마트 포인터 / 참조 계산을 사용하면 상황이 언제 발생하는지 정확하게 알 수 있으므로 사용자가 볼 수있는 것을 코딩하는 동안 알 수 있습니다. GC 설정에서는 가비지 수집기를 가장 많이 제어 할 수 있으며 문제를 해결하기 만하면됩니다.
반면 전체 처리량이 목표라면 메모리 관리에 필요한 리소스를 최소화하기 때문에 GC 기반 시스템이 훨씬 더 나은 선택 일 수 있습니다.
주기 : 나는주기 문제를 중요한 것으로 생각하지 않습니다. 스마트 포인터가있는 시스템에서는주기가없는 데이터 구조를 선호하는 경향이 있거나 단순히 그러한 것들을 어떻게 놓아 두는지주의해야합니다. 필요한 경우, 소유 한 물체의주기를 끊는 방법을 알고있는 골키퍼 물체를 사용하여 자동으로 적절한 파괴를 보장 할 수 있습니다. 일부 프로그래밍 영역에서는 이것이 중요 할 수 있지만 대부분의 일상 작업에서는 관련이 없습니다.
스마트 포인터의 제한 사항 중 하나는 순환 참조에 항상 도움이되는 것은 아닙니다. 예를 들어, 오브젝트 B에 대한 스마트 포인터를 저장하는 오브젝트 A가 있고 오브젝트 B가 오브젝트 A에 대한 스마트 포인터를 저장하고 있습니다. 포인터 중 하나를 재설정하지 않고 함께 남겨두면 할당 해제되지 않습니다.
이는 스마트 포인터가 두 시나리오 모두 프로그램에 도달 할 수 없기 때문에 위 시나리오에서 다루지 않는 특정 조치를 수행해야하기 때문에 발생합니다. 가비지 콜렉션이 처리됩니다. 프로그램에 도달 할 수없는 오브젝트를 올바르게 식별하여 수집됩니다.
스펙트럼 입니다.
성능에 얽매이지 않고 갈기를 준비하고 있다면, 올바른 결정을 내리고 그 자유를 누릴 수있는 모든 책임을지게되면 결국 회의 나 c가 될 것입니다. , 그것을 엉망으로 만드는 모든 자유 :
"내가해야 할 일을 말해 줄게, 넌 믿어."
가비지 콜렉션은 스펙트럼의 다른 끝입니다. 당신은 통제력이 거의 없지만 그것을 돌 보았습니다.
"내가 원하는 것을 말해 줄 것이다.
여기에는 많은 장점이 있습니다. 대부분 자원이 더 이상 필요하지 않은 시점을 정확히 알 때 신뢰할 수있을 필요는 없지만 (여기서 떠 다니는 일부 대답이 있음에도 불구하고) 성능에는 좋지 않습니다. 성능의 예측 가능성. (모든 것을 제어 할 수 있고 어리석은 짓을하면 결과가 더 나빠질 수 있습니다. 그러나 컴파일 타임에 메모리를 확보 할 수있는 조건이 무엇인지 아는 것은 성능 승리로 사용할 수 없습니다. 순진함).
RAII, 범위, 참조 횟수 등 은 모두 스펙트럼을 따라 더 멀리 이동할 수 있도록 도와주는 도우미입니다. 이 모든 것들은 여전히 적극적으로 사용해야합니다. 그들은 여전히 가비지 수집이하지 않는 방식으로 메모리 관리와 상호 작용하도록 허용하고 요구합니다.
결국 모든 것이 CPU 실행 명령으로 귀결된다는 것을 기억하십시오. 내가 아는 한, 모든 소비자 등급 CPU에는 지정된 위치에 데이터를 메모리에 저장해야하고 해당 데이터에 대한 포인터가 있어야하는 명령 세트가 있습니다. 이것이 기본 수준의 전부입니다.
가비지 수집, 이동되었을 수있는 데이터에 대한 참조, 힙 압축 등의 모든 것들은 위의 "주소 포인터가있는 메모리 청크"패러다임에 의해 주어진 제한 내에서 작업을 수행하고 있습니다. 스마트 포인터와 같은 것-실제 하드웨어에서 코드를 실행해야합니다.