가비지 수집기는 수집 할 때마다 전체 메모리가 검색되지 않도록하려면 어떻게합니까?


16

일부 (최소한 Mono 및 .NET) 가비지 수집기는 자주 스캔하는 단기 메모리 영역과 덜 자주 스캔하는 보조 메모리 영역을 가지고 있습니다. 모노는 이것을 보육원이라고 부릅니다.

어떤 객체를 폐기 할 수 있는지 알아 내기 위해 루트, 스택 및 레지스터에서 시작하여 모든 객체를 스캔하고 더 이상 참조되지 않는 모든 객체를 폐기합니다.

내 질문은 그들이 수집 할 때마다 사용중인 모든 메모리가 스캔되지 않도록하는 방법입니다. 원칙적으로 더 이상 사용하지 않는 개체를 찾는 유일한 방법은 모든 개체와 모든 참조를 스캔하는 것입니다. 그러나 이렇게하면 응용 프로그램에서 사용하지 않고 "Nursery Collection"에서도 수행해야하는 엄청난 양의 작업 인 것처럼 느껴지더라도 OS가 메모리를 스왑 아웃하지 못하게됩니다. 보육원을 사용하여 많은 돈을 받고있는 것 같지는 않습니다.

내가 누락되었거나 가비지 수집기가 실제로 모든 객체와 모든 참조를 수집 할 때마다 스캔합니까?


1
좋은 개요는 Angelika Langer가 쓴 The Art of Garbage Collection Tuning 기사에 있습니다. 공식적으로, 그것은 자바에서 어떻게 진행되는지에 관한 것이지만, 제시된 개념은 언어에 구애받지 않습니다.
gnat

답변:


14

세대 별 가비지 콜렉션에서 모든 이전 세대 오브젝트를 스캔하지 않아도되는 기본 관찰은 다음과 같습니다.

  1. 컬렉션 후에도 여전히 존재하는 모든 개체는 최소한의 생성이됩니다 (예 : .net, Gen0 컬렉션 후에는 모든 개체가 Gen1 또는 Gen2이고 Gen1 또는 Gen2 컬렉션 후에는 모든 개체가 Gen2 임).
  2. 모든 것을 N 세대 이상으로 승격시키는 콜렉션 이후로 쓰여지지 않은 오브젝트 또는 그 일부는 더 낮은 세대의 오브젝트에 대한 참조를 포함 할 수 없습니다.
  3. 개체가 특정 세대에 도달 한 경우, 더 낮은 세대를 수집 할 때 개체를 보유하기 위해 도달 가능한 것으로 식별 될 필요가 없습니다.

많은 GC 프레임 워크에서 가비지 수집기가 객체 또는 객체의 일부에 플래그를 지정하여 객체에 대한 첫 번째 쓰기 시도가 수정 된 사실을 기록하는 특수 코드를 트리거 할 수 있습니다. 생성에 관계없이 수정 된 객체 또는 그 일부는 새로운 객체에 대한 참조를 포함 할 수 있으므로 다음 컬렉션에서 스캔해야합니다. 반면에 컬렉션간에 수정되지 않는 오래된 개체가 많이있는 경우가 매우 흔합니다. 저 세대 스캔은 이러한 개체를 무시할 수 있으므로 이러한 스캔을 다른 방법보다 훨씬 빠르게 완료 할 수 있습니다.

btw는 객체가 수정되는 시점을 감지 할 수없고 각 GC 패스의 모든 항목을 스캔해야하더라도 세대 가비지 콜렉션은 여전히 ​​압축 콜렉터의 "스윕"스테이지 성능을 향상시킬 수 있습니다. 일부 임베디드 환경 (특히 순차적 메모리 액세스와 임의 메모리 액세스간에 속도 차이가 거의 없거나 전혀없는 환경)에서 메모리 블록을 이동하는 것은 태그 지정 참조에 비해 상대적으로 비쌉니다. 따라서 세대 별 수집기를 사용하여 "마크"단계를 가속화 할 수 없더라도 "스위프"단계의 속도를 높이는 것이 좋습니다.


메모리 블록을 이동하는 것은 모든 시스템에서 비싸므로, 쿼드 Ghz CPU 시스템에서도 스윕을 향상시키는 것이 좋습니다.
gbjbaanb

@gbjbaanb : 많은 경우에, 객체를 움직이는 것이 완전히 자유 롭더라도 라이브 객체를 찾기 위해 모든 것을 스캔하는 비용은 중요하고 불쾌합니다. 결과적으로 실용적으로는 오래된 물체를 스캔하지 않아야합니다. 반면, 오래된 객체를 압축하지 않는 것은 간단한 프레임 워크에서도 달성 할 수있는 간단한 최적화입니다. BTW는 소형 임베디드 시스템을위한 GC 프레임 워크를 설계하는 경우 불변 개체에 대한 선언적 지원이 도움이 될 수 있습니다. 변경 가능한 객체의 변경 여부를 추적하는 것은 어렵지만 한 가지 방법이 있습니다.
supercat

... 변경 가능한 객체는 모든 GC 패스마다 스캔해야하지만 불변의 객체는 스캔하지 않는다고 가정합니다. 불변 객체를 구성하는 유일한 방법이 가변 공간에 "시제품"을 구성한 다음 복사하는 경우에도 단일 추가 복사 작업으로 향후 GC 작업에서 객체를 스캔 할 필요가 없습니다.
supercat

또한, 1980 년대 마이크로 프로세서에서 6502 마이크로 프로세서 (및 아마도 다른 프로세서)에 대한 BASIC의 구현에서 가비지 수집 성능이 변경되지 않는 많은 문자열을 생성하는 프로그램이 "다음 "문자열 할당"포인터 "문자열 공간의 상단"포인터. 이러한 변경으로 인해 가비지 수집기가 이전 문자열을 검사하여 여전히 필요한지 확인할 수 없습니다. Commodore 64는 거의 첨단 기술이 아니었지만 이러한 "세대 적"GC는 그곳에서도 도움이 될 것입니다.
supercat

7

언급 한 GC는 세대 별 가비지 수집기입니다. 그들은 "유아 사망률"또는 "세대 가설"로 알려진 관측을 최대한 활용하도록 설계되었으므로 대부분의 물체는 매우 빠르게 도달 할 수 없게됩니다. 그들은 실제로 뿌리에서 시작하여 스캔하지만 모든 오래된 객체는 무시합니다 . 따라서 메모리에있는 대부분의 객체를 스캔 할 필요가없고, 어린 객체 만 스캔합니다 (적어도 해당 시점에 도달 할 수없는 오래된 객체는 감지하지 않음).

"그러나 그것은 틀렸다", 나는 당신이 비명을 지르며 "오래된 물건은 어린 물건을 참조 할 수 있고 할 수있다"고 들었습니다. 당신이 옳고 그에 대한 몇 가지 솔루션이 있습니다.이 솔루션은 모두 지식을 얻는 데 빠르고 효율적이며 오래된 개체를 확인하고 무시해도 안전합니다. 그들은 객체를 기록하거나 젊은 세대에 대한 포인터를 포함하는 작은 (객체보다 크지 만 전체 힙보다 훨씬 작은) 메모리 범위로 요약합니다. 다른 사람들은 나보다 훨씬 더 잘 설명 했으므로 카드 마킹, 기억 된 세트, 쓰기 장벽이라는 몇 가지 키워드를 알려 드리겠습니다. 다른 기술들 (하이브리드 포함)도 있지만, 내가 알고있는 일반적인 접근 방식을 포함합니다.


3

어떤 보육 개체가 아직 살아 있는지 확인하려면 수집기는 루트 집합과 마지막 컬렉션 이후에 변형 된 이전 개체 만 스캔하면됩니다. 최근에 변형되지 않은 오래된 개체는 어린 개체를 가리킬 수 없기 때문입니다. . 이 정보를 다양한 수준의 정밀도 (정확한 변형 된 필드 집합에서 돌연변이가 발생할 수있는 페이지 집합)로 유지하기위한 다양한 알고리즘이 있지만, 일반적으로 모든 종류의 쓰기 장벽 : 모든 참조에서 실행되는 코드 GC의 부기를 업데이트하는 유형 필드 변이.


1

가장 오래되고 가장 간단한 가비지 수집기는 실제로 모든 메모리를 검색했으며 다른 모든 처리는 중지하는 동안 중지했습니다. 이후 알고리즘은 복사 / 스캔을 증분 시키거나 병렬로 실행하는 다양한 방식으로이 기능을 개선했습니다. 대부분의 최신 가비지 수집기는 개체를 세대로 분리하고 세대 간 포인터를 신중하게 관리하여 더 오래된 세대를 방해하지 않고 최신 세대를 수집 할 수 있습니다.

요점은 가비지 수집기가 컴파일러 및 나머지 런타임과 긴밀히 협력하여 모든 메모리를보고 있다는 착시를 유지한다는 것입니다.


1970 년대 후반 이전에 미니 컴퓨터와 메인 프레임에서 어떤 가비지 수집 방법이 사용되었는지 확실하지 않지만 적어도 6502 대의 컴퓨터에서 Microsoft BASIC 가비지 수집기는 "다음 문자열"포인터를 메모리 맨 위에 설정 한 다음 검색합니다. "다음 문자열 포인터"아래에있는 가장 높은 주소를 찾기위한 모든 문자열 참조 해당 문자열은 "다음 문자열 포인터"바로 아래에 복사되고 해당 포인터는 바로 아래에 주차됩니다. 그런 다음 알고리즘이 반복됩니다. 포인터가 제공하는 포인터를 징크스하는 코드가 가능했다.
supercat

... 세대 수집과 같은 것. 때로는 각 세대의 최상위 주소를 유지하고 각 GC주기 전후에 몇 가지 포인터 스왑 작업을 추가하여 "세대 적"수집을 구현하기 위해 BASIC을 패치하는 것이 얼마나 어려운지 궁금했습니다. GC 성능은 여전히 ​​나쁘지만 많은 경우에 수십 초에서 10 초로 면도 될 수 있습니다.
supercat

-2

기본적으로 ... GC는 "버킷"을 사용하여 사용중인 것과 사용하지 않는 것을 분리합니다. 일단 확인하면 사용하지 않는 것들을 지우고 다른 모든 것을 2 세대 (1 세대보다 자주 확인되지 않음)로 옮긴 다음 2 세대에서 여전히 사용중인 것들을 3 세대로 옮깁니다.

따라서 3 세대의 경우 일반적으로 어떤 이유로 든 열려있는 객체이며 GC는 자주 확인하지 않습니다.


1
그러나 어떤 객체가 사용되고 있는지 어떻게 알 수 있습니까?
Pieter van Ginkel

도달 가능한 코드에서 도달 할 수있는 개체를 추적합니다. 실행 가능한 코드 (예 : 리턴 된 메소드의 코드)에서 더 이상 객체에 접근 할 수 없으면 GC는 수집이 안전하다는 것을 알게됩니다.
JohnL

둘 다 GC가 어떻게 효율적인지가 아니라 GC가 올바른지 설명하고 있습니다. 질문에서 판단하면 OP는 그것을 잘 알고 있습니다.

@delnan yes Pieter의 의견에 사용 된 객체를 어떻게 알 수 있는지에 대한 질문에 대답했습니다.
JohnL

-5

이 GC에서 일반적으로 사용되는 알고리즘은 Naïve 마크 앤 스윕입니다.

또한 이것이 C # 자체가 아니라 소위 CLR에 의해 관리된다는 사실도 알고 있어야합니다 .


그것이 Mono의 가비지 수집기에 대해 읽은 느낌입니다. 그러나 내가 이해하지 못하는 것은 그들이 수집 한 전체 작업 세트를 스캔하는 경우 GEN-0 수집이 매우 빠른 세대의 수집기를 가지고있는 이유입니다. 2GB의 작동 세트로 어떻게 이것이 빠를 수 있습니까?
Pieter van Ginkel

글쎄, mono의 실제 GC는 Sgen입니다.이 mono-project.com/Generational_GC 또는 일부 온라인 기사 schani.wordpress.com/tag/mono infoq.com/news/2011/01/SGen을 읽으 십시오. 요점은 CLR 및 CLI와 같은이 새로운 기술은 실제로 모듈 식으로 설계되었으며 언어는 이진 코드를 생성하는 방식이 아니라 CLR 용으로 무언가를 표현하는 방식이되었습니다. 귀하의 질문은 알고리즘이 아닌 구현 세부 사항에 관한 것입니다. 알고리즘에는 여전히 구현이 없기 때문에 Mono의 기술 문서와 기사를 읽어야합니다.
user827992

혼란 스러워요. 가비지 수집기가 사용하는 전략은 알고리즘이 아닙니까?
Pieter van Ginkel

2
-1 혼란스러운 OP를 중지합니다. GC가 CLR의 일부이며 언어별로 다르지 않다는 것은 전혀 관련이 없습니다. GC로 대부분이 힙 레이아웃 및 도달 가능성을 결정하는 방법을 특징으로하며, 후자는 모든 것을 사용하는 알고리즘 (들)에 대해. 많은 알고리즘 구현이있을 수 있지만 구현 세부 사항에 얽매이지 않아야하지만 알고리즘만으로도 스캔되는 개체 수를 결정합니다. 세대 별 GC는 단순히 알고리즘 + 힙 레이아웃으로, "대부분의 개체는 어리게 죽는" "가설"을 활용하려고합니다. 이들은 순진하지 않습니다.

4
Algorithm! = 구현은 실제로 구현하지만 다른 알고리즘의 구현이되기 전에는 구현이 이탈 할 수 있습니다. GC 세계에서 알고리즘 설명은 매우 구체적이며 보육원 수집에서 전체 힙을 스캔하지 않는 것과 세대 간 포인터를 찾고 저장하는 방법을 포함합니다. 알고리즘이 알고리즘의 특정 단계에 걸리는 시간을 알려주지는 않지만이 질문과는 전혀 관련이 없습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.