Go에서의 GC 알고리즘
Tricolor mark and sweep algorithm
- 삼색 표시 알고리즘이라고도 하는데, 그냥 삼색 알고리즘이라 부를련다
- 각 색깔에 대해선 아래와 같다
- White : 프로그램에 더 이상 접근할 수 없어 GC 대상이 되는 상태
- Black : 프로그램에서 사용 중이고, White 객체 참조는 없는 상태
- Grey : 프로그램에서 사용 중인데, White 객체를 참조할 수도 있어 검사를 진행해야 하는 상태
- 각 색깔 마킹에 대한 스텝은 다음과 같다
- GC에선 모든 객체를 White로 세팅
- GC가 루트 객체부터 방문하면서 Grey로 세팅
- Grey로 세팅한 객체를 Black으로 변경하면서 참조한 객체가 White면 Grey로 업데이트
- 위 작업을 Grey 상태가 없어질때 까지 반복, 이후엔 White 객체는 GC 수행
- GC 수행 외 어플리케이션의 모든 스레드들을 Mutator threads 라고 하는데, GC 안전 보장을 위해 Mutator threads를 중지 시키고 GC를 수행한다
- 허나 이렇게 되면 성능 저하가 발생하기에 Go에선 CMS (Concurrent Mark and Sweep) 액션으로 GC로 인한 지연을 최소한으로 낮춰주는데, 이는 메모리 단편화로 인해 메모리 할당 속도가 느려진다고 한다
Non-generational algorithm
- 일단 Generational 알고리즘은 “새로 생성된 객체가 오래된 객체에 비해 수명이 짧다”는 가정을 두는데, 자바에서 쓰는 GC 동작처럼 YG, OG로 나누어 GC를 수행하게 된다
- Generational 알고리즘 문제점이 OG에서 YG로 참조하는 객체가 있을 때, YG GC만 수행, Minor GC를 수행해버리면 문제가 될 수 있는데, 이를 방지하기 위해 Write Barrier 라고 저런 객체들을 따로 기록하는 테이블로 관리하는 개념이다
- 근데 Go에선 Write Barrier를 허용할 수 없어서 Generational 알고리즘을 택하지 않았는데, 이는 Go 컴파일러 분석 결과, 대부분 객체들이 힙이 아닌 스택에 메모리를 할당하기에 Generational 알고리즘 의미가 없다고 판단했기 때문이라고 한다
Non-compaction algorithm
- GC는 보통 압축, 비압축으로도 나뉘는데, Go에선 비압축 방식을 택했다고 한다
- 일단 압축 방식은 GC 후 메모리 파편화 방지를 위해 재배치를 하여 빠른 메모리 할당을 해줄 수 있다
- 그런데 Go는 비압축을 택했는데, 보통 비압축 방식이 메모리 할당과 GC 반복으로 인해 힙 메모리가 파편화되어 성능이 악화되는걸로 알고 있다, 그런데 왜 비압축일까? 그건 tcmalloc 라이브러리를 도입하면서 해결했다고 한다