>  기사  >  백엔드 개발  >  Golang GC 가비지 수집 메커니즘에 대한 자세한 설명

Golang GC 가비지 수집 메커니즘에 대한 자세한 설명

藏色散人
藏色散人앞으로
2020-09-14 09:47:083266검색

다음은 golang 튜토리얼 칼럼에 나온 Golang GC 가비지 수집 메커니즘에 대한 자세한 설명입니다. 도움이 필요한 친구들에게 도움이 되길 바랍니다!

Golang GC 가비지 수집 메커니즘에 대한 자세한 설명

Abstract

실제로 Go 언어를 사용하는 과정에서 겉보기에 이상한 메모리 사용 현상을 접하게 되었고, 그래서 Go 언어의 가비지 컬렉션 모델에 대해 연구를 해보기로 했습니다. 이 기사에는 연구 결과가 요약되어 있습니다.

가비지 수집이란 무엇인가요?

옛날에는 애플리케이션을 개발하는 프로그래머에게 메모리 관리가 주요 문제였습니다. 전통적인 시스템 수준 프로그래밍 언어(주로 C/C++)에서 프로그래머는 메모리를 신중하게 관리하고 메모리의 적용 및 해제를 제어해야 합니다. 주의하지 않으면 메모리 누수가 발생할 수 있습니다. 이런 종류의 문제는 찾기도 어렵고 개발자에게도 항상 악몽이었습니다. 이 두통 문제를 해결하는 방법은 무엇입니까? 과거에는

  • 메모리 누수 감지 도구라는 두 가지 방법이 일반적으로 사용되었습니다. 이 도구의 원리는 일반적으로 스캐너를 통해 메모리 누수를 일으킬 수 있는 코드 세그먼트를 감지하는 정적 코드 스캐닝입니다. 그러나 탐지 도구에는 필연적으로 누락과 단점이 있으며 보조적인 역할만 할 수 있습니다.
  • 스마트 포인터. 이는 C++에서 도입된 자동 메모리 관리 방법으로, 자동 메모리 관리 기능을 통해 포인터 개체를 통해 개체를 참조함으로써 프로그래머는 메모리 해제에 너무 많은 주의를 기울일 필요가 없으며 메모리 자동 해제 목적을 달성합니다. 이 방법은 가장 널리 사용되지만 프로그래머에게는 일정한 학습 비용이 들고(언어 수준에서는 기본적으로 지원되지 않음) 사용하는 것을 잊어버린 시나리오가 발생하면 메모리 누수를 피할 수 없습니다.

이 문제를 해결하기 위해 이후에 개발되는 거의 모든 새로운 언어(java, python, php 등)에는 언어 수준에서 자동 메모리 관리가 도입되었습니다. 즉, 언어 사용자는 주의만 기울이면 됩니다. 메모리 애플리케이션에는 신경쓰지 않아도 됩니다. 메모리 해제는 가상 머신이나 런타임에 의해 자동으로 관리됩니다. 더 이상 사용하지 않는 메모리 자원을 자동으로 재활용하는 것을 가비지 수집이라고 합니다.

일반적인 가비지 수집 방법

참조 카운팅

이것은 앞서 언급한 스마트 포인터와 유사한 가장 간단한 가비지 수집 알고리즘입니다. 각 개체에 대한 참조 카운트를 유지합니다. 개체를 참조하는 개체가 삭제되거나 업데이트되면 참조 개체의 참조 카운트는 자동으로 1씩 감소합니다. 참조 개체가 생성되거나 다른 개체에 할당되면 참조 카운트는 자동으로 증가합니다. 하나씩. 참조 횟수가 0에 도달하면 객체가 즉시 재활용됩니다.

이 방법의 장점은 구현이 간단하고 메모리가 적시에 재활용된다는 것입니다. 이 알고리즘은 iOS 코코아 프레임워크, PHP, Python 등과 같이 메모리가 부족하고 실시간 성능이 높은 시스템에서 널리 사용됩니다. 간단한 참조 카운트 알고리즘에는 다음과 같은 명백한 단점도 있습니다.

  • 참조 카운트를 자주 업데이트하면 성능이 저하됩니다. 간단한 해결책은 컴파일러가 인접한 참조 카운트 업데이트 작업을 하나의 업데이트로 병합하는 것입니다. 또 다른 방법은 빈번한 임시 변수 참조를 계산하는 것이 아니라 스택을 스캔하여 참조가 0에 도달하는지 확인하는 것입니다. 풀어줄지 말지. 그 외에도 다양한 방법이 있으니 자세한 내용은 여기를 참고해주세요.
  • 순환 참조 문제. 객체 간에 순환 참조가 발생하면 참조 체인의 객체를 해제할 수 없습니다. 가장 확실한 해결책은 순환 참조를 피하는 것입니다. 예를 들어 Cocoa는 강력한 포인터와 약한 포인터라는 두 가지 포인터 유형을 도입합니다. 또는 시스템이 순환 참조를 감지하고 사전에 순환 체인을 끊습니다. 물론 이는 가비지 수집의 복잡성도 증가시킵니다.

마크 및 스윕

이 방법은 두 단계로 나누어집니다. 마크는 루트 변수에서 시작하여 애플리케이션 순회를 통해 액세스할 수 있는 모든 참조 개체를 반복합니다. "; 마킹이 완료된 후. , 지우기 작업이 수행되고 표시되지 않은 메모리가 재활용됩니다(재활용에는 조각 모음 작업이 수반될 수 있음). 이 방법은 참조 카운팅의 단점을 해결하지만 명백한 문제도 있습니다. 가비지 수집이 시작될 때마다 현재의 모든 일반 코드 실행이 일시 중지되고 재활용으로 인해 시스템의 응답성이 크게 저하됩니다! 물론 이 문제를 최적화하기 위해 mark&sweep 알고리즘(예: 3색 표시 방법)의 다양한 변형이 등장했습니다.

세대 컬렉션(세대)

많은 실제 관찰을 통해 우리는 객체 지향 프로그래밍 언어에서 대부분 객체의 수명 주기가 매우 짧다는 것을 알게 되었습니다. 세대별 수집의 기본 아이디어는 힙을 세대라고 불리는 둘 이상의 공간으로 나누는 것입니다. 새로 생성된 객체는 Young Generation이라는 곳에 저장됩니다(일반적으로 Young Generation의 크기는 Old Generation보다 훨씬 작습니다). 반복적인 가비지 수집 실행으로 수명 주기가 더 긴 객체가 승격됩니다(승격). ) 구세대에게. 따라서 두 가지 서로 다른 가비지 수집 방법인 신세대 가비지 수집과 구세대 가비지 수집이 등장했으며, 이는 각자의 공간에 있는 객체에 대한 가비지 수집을 수행하는 데 사용됩니다. 신세대의 가비지 수집 속도는 매우 빠르며, 이전 세대에 비해 몇 배 더 빠릅니다. 신세대의 가비지 수집 빈도가 더 높더라도 실행 효율성은 이전 세대보다 여전히 좋습니다. 대부분의 개체의 수명 주기가 매우 짧기 때문에 이전 세대로 승격할 필요가 전혀 없습니다.

GO의 가비지 수집기

Go 언어 가비지 수집은 일반적으로 고전적인 표시 및 스윕 알고리즘을 사용합니다.

  • 버전 1.3 이전에는 golang의 가비지 수집 알고리즘이 매우 투박했고 성능도 널리 비판받았습니다. 특정 조건(메모리가 임계값을 초과하거나 주기적으로 2분 등)에서 go 런타임은 모든 작업의 ​​실행을 일시 중지하고 표시 및 청소를 수행합니다. 작업이 완료된 후 모든 작업의 ​​실행을 시작합니다. 많은 메모리가 사용되는 시나리오에서 Go 프로그램은 가비지 수집을 수행할 때 매우 명백한 정지 현상(Stop The World)을 경험하게 됩니다. 높은 응답 속도를 요구하는 백그라운드 서비스 프로세스에서 이러한 지연은 도저히 용납할 수 없습니다! 이 기간 동안 프로덕션 환경에서 Go 언어를 연습하던 국내외 많은 팀은 어느 정도 GC의 함정을 밟았습니다. 당시 이 문제를 해결하기 위한 일반적인 방법은 자동 할당되는 메모리 양을 최대한 빠르게 제어하여 gc 로드를 줄이고, 수동 메모리 관리를 사용하여 대용량 메모리와 높은 빈도 할당이 필요한 시나리오를 처리하는 것이었습니다.
  • Go 팀은 버전 1.3부터 ​​지속적으로 GC 성능을 개선하고 최적화하기 시작했습니다. Go의 새 버전이 출시될 때마다 GC 개선은 모두의 관심의 초점이 되었습니다. 버전 1.3에서는 go 런타임이 마크 작업과 스윕 작업을 분리합니다. 이전과 마찬가지로 먼저 모든 작업의 ​​실행을 일시 중지하고 마크가 완료된 후 즉시 스윕 작업을 다시 시작합니다. 일반적인 코루틴 작업은 다른 작업과 동시에 실행됩니다. 멀티 코어 프로세서에서 실행 중인 경우 go는 비즈니스 코드 실행에 영향을 주지 않고 별도의 코어에서 gc 작업을 실행하려고 시도합니다. Go 팀 자체 설명에 따르면 일시정지 시간이 50%-70% 감소했다고 합니다.
  • 버전 1.4(최신 안정 버전)에는 GC에 대한 성능 변경 사항이 많지 않습니다. 버전 1.4에서는 많은 런타임 코드가 기본 C 언어 구현을 Go 언어 구현으로 대체했습니다. gc에 가져온 주요 변경 사항은 정확한 gc를 달성할 수 있다는 것입니다. C 언어 구현은 gc 중에 메모리의 객체 정보를 얻을 수 없으므로 일반 변수와 포인터를 정확하게 구분할 수 없습니다. 우연히 이 일반 변수가 가리키는 공간에 다른 객체가 있는 경우에만 포인터로 처리할 수 있습니다. 이면 이 개체는 재활용되지 않습니다. Go 언어 구현은 객체의 유형 정보를 완전히 알고 있으며 표시 시 포인터가 가리키는 객체만 순회하므로 C 구현에서 힙 메모리 낭비를 방지합니다(약 10-30% 해결).
  • 버전 1.5에서 go 팀은 GC를 크게 개선했습니다(예상은 쓰기 장벽 도입과 같이 1.4에 마련되었습니다). 공식적인 주요 목표는 지연을 줄이는 것입니다. Go 1.5에서 구현되는 가비지 수집기는 "비세대, 비이동, 동시, 3색 표시 및 청소 가비지 수집기"입니다. 세대별 알고리즘은 위에서 언급한 더 나은 가비지 수집 관리 전략이지만 버전 1.5에서는 구현되지 않았습니다. 내 생각에는 단계가 너무 클 수 없으며 점진적으로 개선되어야 한다고 합니다. 버전 1.6의 gc 최적화에서 고려됨에 구현됩니다. 동시에 위에서 소개한 3색 마킹 방식을 도입하여 매번 전체 메모리 공간을 스캔하지 않고 점진적으로 마킹 작업을 수행할 수 있어 세상을 멈추는 시간을 단축할 수 있다. 이를 통해 Go의 가비지 수집 성능이 버전 1.5까지 향상되었음을 알 수 있습니다. 그러나 상대적으로 성숙한 가비지 수집 시스템(예: Java jvm 및 javascript v8)의 경우 Go를 최적화하려면 아직 갈 길이 멀습니다. 나는 미래가 분명 멋질 것이라고 믿습니다~).

실제 경험

Go 언어를 연습할 때 팀이 직면하는 가장 일반적이고 어려운 문제는 메모리 문제(주로 gc)입니다. 여기에는 누구나 직면한 문제와 경험이 요약되어 있습니다. .

Go 프로그램의 대용량 메모리 사용 문제

이 문제는 백그라운드 서비스에 대한 스트레스 테스트를 수행했을 때 발견되었습니다. 이때 각 서비스 모듈에 대한 많은 사용자 요청을 시뮬레이션했습니다. 메모리 사용량이 눈에 띄게 증가하는 것을 볼 수 있습니다. 하지만 스트레스 테스트를 중단해도 메모리 사용량은 크게 떨어지지 않았다. gprof 등 다양한 방법을 이용하여 문제점을 찾아내는데 오랜 시간이 걸렸지만, 여전히 원인은 발견되지 않았습니다. 드디어 이때가 정상이라는 걸 알게 됐는데... 이유는 크게 두 가지인데,

첫 번째, go의 가비지 컬렉션에는 트리거 임계값이 있는데, 이는 각 메모리 사용량이 증가함에 따라 점차 증가합니다(예를 들어 초기 임계값은 10MB이고 다음 번에는 20MB, 다음 번에는 40MB가 됩니다... ), gc go가 오랫동안 트리거되지 않으면 적극적으로 한 번(2분) 트리거됩니다. 피크 시간 동안 메모리 사용량이 증가한 후에는 계속해서 메모리를 신청하지 않으면 임계값에 따라 gc를 트리거하는 것이 거의 불가능합니다. 대신 gc를 트리거하기 전에 active gc가 시작될 때까지 최대 2분을 기다려야 합니다.

두 번째 이유는 Go 언어가 메모리를 시스템에 반환할 때 시스템에 메모리가 더 이상 필요하지 않으며 동시에 재활용될 수 있음을 알리는 동시에 운영 체제가 "지연" 전략을 채택한다는 것입니다. , 즉시 재활용하는 것이 아니라 시스템 메모리가 부족할 때까지 기다려야만 재활용이 시작되므로 프로그램이 메모리를 다시 적용할 때 매우 빠른 할당 속도를 얻을 수 있습니다.

긴 GC 시간 문제

사용자가 이벤트에 응답해야 하는 백엔드 프로그램의 경우 golang GC 중에 파트타임으로 월드를 중지하는 것은 악몽입니다. 위의 소개에 따르면, 위의 개선이 완료되면 go 버전 1.5의 gc 성능이 많이 향상될 것입니다. 그러나 모든 가비지 수집 언어는 필연적으로 gc 중에 성능 저하를 겪게 될 것입니다. 임시 힙을 자주 생성하여 가비지 수집 중 검색 시간을 줄입니다. 자주 사용해야 하는 임시 개체의 경우 배열 캐시를 통해 직접 재사용하는 것이 좋습니다. 메모리 자체를 관리하고 가비지 수집을 우회하려면 cgo 방법을 사용하십시오. 이 방법은 꼭 필요한 경우가 아니면 권장되지 않습니다(예측할 수 없는 문제가 쉽게 발생할 수 있음). 물론 꼭 필요한 경우에도 고려할 수 있습니다. 이 방법은 여전히 ​​매우 명확합니다~

고루틴 누출 문제

us 서비스는 많은 긴 연결 요청을 처리해야 합니다. 구현 중에 각 긴 연결 요청에 대해 읽기 및 쓰기 코루틴이 열리고 끝없는 for 루프가 열립니다. 지속적으로 데이터를 보내고 받는데 사용됩니다. 원격 측에서 연결을 닫을 때 이 두 코루틴이 처리되지 않으면 계속 실행되고 점유된 채널이 해제되지 않습니다... 여기서는 매우 주의해야 하며 사용하지 않은 후에는 제거해야 합니다. 종속 채널을 닫고 종료를 보장하기 위해 코루틴에서 채널이 닫혀 있는지 확인합니다.

Golang-gc의 기본 지식

APR 30TH, 2016 8:02 PM | COMMENTS

이 부분에서는 주로 golang gc에 대한 입문 지식을 소개합니다. 내용이 많기 때문에 조금씩 정리하겠습니다.

Golang GC의 배경

  • golang은 디자인 원칙인 가비지 컬렉션을 기반으로 한 언어입니다.
  • 가비지 컬렉터가 있는 언어로서 프로그램과 gc 상호 작용의 효율성은 전체 프로그램의 운영 효율성에 영향을 미칩니다.
  • 보통 프로그램 자체의 메모리 관리는 gc와 프로그램 간의 효율성에 영향을 미치며 심지어 성능 병목 현상을 일으키기도 합니다.

Golang GC 관련 문제

주된 참고 자료는 다음과 같습니다.

http://morsmachine.dk/machine-gc

2014년에 작성된 글입니다. 당시의 GC 메커니즘은 비교적 단순했던 것으로 추정됩니다. golang 새 버전 gc에 대한 변경 사항은 상대적으로 클 것입니다

go 언어 읽기 노트에 golang gc에 관한 관련 부분도 있습니다

메모리 누수에 대하여

"Memory Leak"라는 단어가 저에게 익숙한 것 같습니다. 그러나 사실 나는 그 정확한 의미를 본 적이 없습니다.

메모리 누수는 운영체제 관점에서 설명하면 "운영체제가 모든 프로세스에 제공할 수 있는 저장 공간(가상 메모리 공간)이 특정 프로세스에 의해 소모되고 있다"는 것입니다. 프로그램이 실행 중일 때 지속적으로 저장 공간을 동적으로 열고 이러한 저장 공간은 실행이 완료된 후에도 시간 내에 해제되지 않습니다. 응용 프로그램이 특정 메모리 세그먼트를 할당한 후 설계 오류로 인해 프로그램이 메모리 세그먼트에 대한 제어를 상실하여 메모리 공간이 낭비될 수 있습니다.

프로그램이 메모리 공간의 메모리 조각에 적용되고 프로그램 실행이 완료된 후에도 메모리 공간이 해제되지 않고 해당 프로그램에 프로그램이 적용한 공간을 재활용할 수 있는 좋은 gc 메커니즘이 없는 경우 , 이로 인해 메모리 누수가 발생합니다.

사용자 입장에서는 메모리 누수 자체는 사용자 기능에 영향을 주지 않기 때문에 아무런 해가 되지 않지만, "메모리 누수"가 발생한 경우

C, C++ 등 Garbage Collection 기능이 없는 언어의 경우 , 우리는 주로 두 가지 유형의 메모리 누수에 중점을 둡니다:

  • Heap 누수. 메모리는 프로그램 실행 시 필요에 따라 malloc, realloc new 등을 통해 힙에서 할당된 메모리 조각을 의미하며, 완료 후에는 해당 free 또는 delete를 호출하여 삭제해야 합니다. 프로그램의 설계 오류로 인해 메모리의 이 부분이 해제되지 않으면 앞으로 이 메모리가 사용되지 않으며 Heap Leak이 발생하게 됩니다.
  • 시스템 리소스 누수(Resource Leak)를 주로 말합니다. Bitmap, Handle, SOCKET 등 시스템에서 할당한 자원을 사용하는 프로그램이 해당 기능을 사용하여 해제되지 않아 시스템 자원이 낭비되어 시스템 성능 저하 및 시스템 작동 불안정을 심각하게 초래할 수 있습니다.

메모리 누수와 관련된 많은 관련 문제가 있지만 여기서는 논의하지 않습니다.

일반적인 GC 모드

여기에는 일반적인 소개가 나와 있습니다.

  • Reference counting(참조 카운팅) 각 객체는 참조 카운터를 유지합니다. 해당 객체를 참조하는 객체가 소멸되거나 업데이트되면 참조된 객체의 참조 카운터가 자동으로 1씩 감소합니다. 적용된 객체가 생성되거나 할당될 때. 다른 객체에 대한 참조는 +1이고 참조가 0이면 재활용됩니다. 아이디어는 간단하지만 참조 카운터를 자주 업데이트하면 참조하는 루프가 있습니다(php, Python에서 사용)
  • Mark and Sweep(마크 앤 스윕)은 golang입니다. 사용되는 것은 루트 변수에서 참조된 모든 객체를 탐색하고, 마킹 후 지우기 작업을 수행하고, 표시되지 않은 객체를 재활용하는 것입니다. 단점: 가비지 수집이 수행될 때마다 정상적으로 실행되는 모든 코드가 일시 중지됩니다. , 시스템의 응답성이 크게 감소하고 다양한 표시 및 늪 변형(3색 표시 방식)이 적용되어 성능 문제가 완화됩니다.
  • 세대 수집(세대) jvm은 세대 재활용이라는 아이디어를 사용합니다. 객체지향 프로그래밍 언어에서 대부분의 객체의 수명주기는 매우 짧습니다. 세대별 수집의 기본 아이디어는 힙을 세대라고 불리는 둘 이상의 공간으로 나누는 것입니다. 새로 생성된 객체는 young Generation이라는 곳에 저장됩니다. (일반적으로 Young Generation의 크기는 Old Generation보다 훨씬 작습니다.) 반복적인 가비지 수집 실행으로 인해 수명 주기가 더 긴 객체가 Promotion됩니다. 노년까지 (여기서는 과학적 사고의 기본 아이디어이기도 한 분류 아이디어가 사용됩니다).

따라서 신세대 가비지 수집과 구세대 가비지 수집이라는 두 가지 서로 다른 가비지 수집 방법이 탄생했습니다(먼저 분류한 다음 올바른 약을 처방). 이는 각자의 공간에 있는 물체에 대한 가비지 수집을 수행하는 데 사용됩니다. 신세대의 가비지 수집 속도는 매우 빠르며, 이전 세대에 비해 몇 배 더 빠릅니다. 신세대의 가비지 수집 빈도가 더 높더라도 실행 효율성은 이전 세대보다 여전히 좋습니다. 대부분의 개체의 수명 주기가 매우 짧기 때문에 이전 세대로 승격할 필요가 전혀 없습니다.

golang의 gc가 일반적으로 작동하는 방식

golang의 gc는 기본적으로 마크 지우기 아이디어입니다.

메모리 힙에서(때때로 메모리 페이지를 관리할 때 힙 데이터 구조가 사용되기 때문에 (힙 메모리라고 함) 저장소 이러한 개체 간에 참조가 있을 수 있는 일련의 개체 추적 가비지 수집기는 특정 시점에 실행 중인 프로그램을 중지한 다음 런타임을 검색합니다. 이미 알려진 개체 집합, 일반적으로 전역 변수 및 다양한 개체입니다. 스택에 존재하는 것입니다. gc는 이러한 객체를 표시하고, 이러한 객체의 상태를 도달 가능한 것으로 표시하고, 현재 객체에서 도달할 수 있는 다른 위치에 있는 객체의 모든 참조를 찾아 이러한 객체를 도달 가능한 객체로 표시합니다. 이 단계를 표시 단계라고 합니다. mark 단계입니다. 이 단계의 주요 목적은 이러한 개체의 상태 정보를 얻는 것입니다.

이러한 개체가 모두 검색되면 gc는 연결할 수 없는 개체(접근할 수 없는 상태의 개체)를 모두 가져와서 재활용합니다. 이 단계를 sweeping 단계라고 합니다.

gc는 도달 가능으로 표시되지 않은 객체만 수집합니다. gc가 참조를 인식하지 못하면 여전히 사용 중인 객체가 결국 재활용되어 프로그램 실행 오류가 발생할 수 있습니다.

스캔, 재활용, 청소의 세 가지 주요 단계를 볼 수 있습니다.

다른 언어에 비해 golang의 가비지 수집 모델은 비교적 간단하다고 생각합니다.

Problems in gc

gc는 메모리 재활용 문제를 해결하기 위해 도입되었다고 할 수 있습니다. 새로 개발된 언어(java, python, php 등)를 사용할 때 사용자는 메모리 객체의 해제에 신경쓰지 않고, 관련 작업을 런타임이나 VM에서 수행함으로써 객체의 적용에만 신경쓰면 됩니다. , 메모리 공간을 자동으로 관리하는 효과를 얻기 위해 더 이상 사용되지 않는 메모리 자원을 자동으로 재활용하는 이러한 동작을 가비지 수집이라고 합니다. 앞서 언급한 바에 따르면, gc가 참조를 제대로 식별할 수 있는지 여부는 gc가 정상적으로 작동하는 기초입니다. 따라서 첫 번째 질문은 GC가 참조를 어떻게 식별해야 하는가입니다.

가장 큰 문제: 참조를 식별하기가 어렵습니다. 기계어가 무엇인지, 참조로 간주되는 것이 무엇인지 알기가 어렵습니다. 참조가 실수로 누락된 경우 해제할 준비가 되지 않은 메모리가 이제 실수로 해제되므로 전략은 그 이상을 선택하는 것입니다.

한 가지 전략은 모든 메모리 공간을 가능한 참조(포인터 값)로 처리하는 것입니다. 이것을 보수적 가비지 수집기(보수적 가비지 수집기)라고 합니다. 이것이 C의 Boehm 가비지 수집기가 작동하는 방식입니다. 즉, 메모리의 일반 변수는 포인터처럼 취급되며 모든 포인터를 포괄하려고 합니다. 만약 일반 변수 값이 가리키는 공간에 다른 객체가 있는 경우 이 객체는 재활용되지 않습니다. Go 언어 구현은 객체의 유형 정보를 완전히 인식하고 표시 시 포인터가 가리키는 객체만 순회하므로 C 구현에서 힙 메모리 낭비를 방지합니다(약 10-30% 해결).

3색 마킹

2014/6 1.3 동시 청소 소개(가비지 수집과 사용자 로직의 동시 실행?)

2015/8 1.5 3색 마킹 소개

동시 청소 도입에 대해서는 여기를 참조하세요. 버전 1.3에서는 go 런타임이 표시 작업과 스윕 작업을 분리합니다. 이전과 마찬가지로 먼저 모든 작업의 ​​실행을 일시 중지하고 표시를 시작합니다(표시 부분에서는 표시가 완료된 후에도 여전히 원래 프로그램을 중지해야 합니다). 작업을 즉시 다시 시작하고 스윕 작업을 일반 코루틴 작업처럼 병렬로 실행하고 다른 작업과 함께 실행하도록 합니다. 멀티 코어 프로세서에서 실행되는 경우 go는 비즈니스 코드 실행에 영향을 주지 않고 별도의 코어에서 gc 작업을 실행하려고 시도합니다. go 팀 자체는 일시 중지 시간을 50%-70% 줄인다고 말합니다.

기본 알고리즘은 앞서 언급한 청소 + 재활용입니다. Golang gc 최적화의 핵심은 STW(Stop The World) 시간을 점점 더 짧게 만들려고 노력하는 것입니다.

GC를 측정하는 방법

전에 너무 많이 말씀드렸는데, GC의 스타 효율성을 측정하고 프로그램 실행에 영향을 미치는지 여부를 판단하는 방법은 무엇인가요? 첫 번째 방법은 godebug의 환경 변수를 설정하는 것입니다. 자세한 내용은 이 문서를 참조하세요. 링크는 GODEBUG=gctrace=1 ./myserver입니다. , 원하는 경우 출력 결과를 이해하려면 gc의 원리에 대한 심층적인 분석을 수행해야 합니다. 이 기사의 장점은 golang의 gc 시간을 결정하는 요소가 무엇인지 명확하게 보여 준다는 것입니다. GC 시간을 개선하는 방법도 있습니다. GODEBUG=gctrace=1 ./myserver,如果要想对于输出结果了解,还需要对于 gc 的原理进行更进一步的深入分析,这篇文章的好处在于,清晰的之处了 golang 的 gc 时间是由哪些因素决定的,因此也可以针对性的采取不同的方式提升 gc 的时间:

根据之前的分析也可以知道,golang 中的 gc 是使用标记清楚法,所以 gc 的总时间为:

Tgc = Tseq + Tmark + Tsweep

이전 분석에 따르면 golang의 GC는 Clear Mark 방식을 사용하므로 GC의 총 시간은 다음과 같습니다.
  • Tgc = Tseq + Tmark + Tsweep (T는 시간을 나타냄)
  • Tseq는 사용자의 고루틴을 중지하고 몇 가지 준비 활동을 수행하는 데 필요한 시간을 나타냅니다(보통 작음).
  • Tmark는 힙 마킹 시간입니다. 사용자 고루틴이 중지되므로 처리 지연에 큰 영향을 미칠 수 있습니다.

Tsweep은 일반적으로 일반 프로그램 실행과 동시에 발생하므로 지연에 중요하지 않습니다.

  • 세분성은 더 세분화됩니다. , 구체적인 개념은 아직 불분명합니다.
  • Tmark 관련: 1. 가비지 수집 프로세스 중에 힙에 있는 활성 객체의 수, 2. 포인터가 있는 활성 객체가 차지하는 총 메모리 양, 3. 활성 개체의 포인터 수입니다.

Tsweep 관련: 1 힙 메모리의 총량 2 힙에 있는 쓰레기의 총량

GC 튜닝 수행 방법(Gopher Conference Danny)

하드 매개변수

알고리즘 문제와 관련하여 항상 몇 가지 매개변수가 있습니다. . GOGC 매개변수는 주로 다음 gc가 시작될 때 메모리 사용량을 제어합니다.

예를 들어, 현재 프로그램은 4M의 메모리를 사용합니다(여기서는 힙 메모리

에 대해 이야기함). 이는 프로그램이 현재 도달할 수 있는 메모리가 4M임을 의미합니다. 프로그램이 차지하는 메모리가 도달 가능*(1+ GOGC/100)= 8M이 되면 gc가 트리거되고 관련 gc 작업이 시작됩니다.

GOGC 매개변수를 설정하는 방법은 GC 빈도를 줄이기 위해 GOGC 매개변수를 늘리는 등 실제 생산 시나리오에 따라 결정해야 합니다.

Tips

깊이 있는 통찰력을 갖고 싶다면 gdb를 사용하는 것이 필수적입니다. 이 글에서는 gdb 사용에 대한 몇 가지 소개 팁을 정리했습니다.

객체 할당 감소

소위 객체 할당 감소는 실제로 객체를 재사용하려는 것입니다. 예를 들어 다음 두 함수 정의는 다음과 같습니다.

첫 번째 함수에는 형식적인 매개 변수가 없으며 호출될 때마다 []byte를 반환합니다. 두 번째 함수는 호출될 때마다 buf []byte 유형을 갖습니다. 읽은 바이트 수를 반환합니다.

첫 번째 함수는 호출될 때마다 공간을 할당하므로 gc에 추가적인 부담이 발생합니다. 두 번째 함수는 호출될 때마다 형식 매개변수 선언을 재사용합니다.

문자열 및 []바이트 변환에 대한 일반적인 이야기

Stirng와 []byte 간 변환은 gc에 부담을 줍니다. gdb를 통해 먼저 두 가지의 데이터 구조를 비교할 수 있습니다. 🎜

둘이 변환되면 기본 데이터 구조가 복사되므로 GC 효율성이 낮아집니다. 솔루션 전략 측면에서 한 가지 방법은 항상 []byte를 사용하는 것입니다. 특히 데이터 전송에서는 []byte에도 문자열에서 일반적으로 사용되는 효과적인 연산이 많이 포함되어 있습니다. 다른 하나는 복사를 피하기 위해 하위 수준 작업을 사용하여 직접 변환하는 것입니다. 직접 변환을 위해 unsafe.Pointer를 주로 사용하는 WeChat "Yuhen Academy"의 성능 최적화 첫 번째 부분을 참조할 수 있습니다.

unsafe 사용에 대해서는 별도의 기사를 작성하면 될 것 같습니다. 먼저 여기에 관련 정보를 나열하세요. http://studygolang.com/articles/685 직관적으로 unsafe.Pointer는 C++ void*로 이해될 수 있습니다. golang은 다양한 유형의 포인터 변환을 위한 브리지와 동일합니다.

uintptr의 기본 유형은 포인터가 가리키는 주소 값을 보유할 수 있는 int입니다. unsafe.Pointer로 변환될 수 있으며, 주요 차이점은 uintptr이 포인터 작업에 참여할 수 있는 반면 unsafe.Pointer는 포인터 변환만 수행할 수 있고 포인터 작업을 수행할 수 없다는 것입니다. 포인터 연산에 golang을 사용하고 싶다면 이것을 참고하면 됩니다. 특정 포인터 작업을 수행하는 경우 오프셋 등과 같은 추가 계산을 수행하기 전에 먼저 uintptr 유형으로 변환해야 합니다.

문자열 연결에는 +를 아껴서 사용하세요 +를 사용하여 문자열을 연결하면 새로운 객체가 생성되고 gc의 효율성이 떨어지기 때문에 가장 좋은 방법은 추가 기능을 사용하는 것입니다.

하지만 또 다른 단점이 있는데, 예를 들어 다음 코드를 참고하세요.

append 연산을 사용하고 나면 배열의 공간이 1024에서 1312로 늘어나기 때문에 배열의 길이를 미리 알 수 있다면, 처음 할당할 때 공간을 할당하는 것이 가장 좋습니다. 공간 계획을 잘 수행하면 코드 관리 비용이 증가하는 동시에 gc에 대한 부담이 줄어들고 코드 효율성이 향상됩니다.

위 내용은 Golang GC 가비지 수집 메커니즘에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 csdn.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제