반응형
가비지 컬렉터(GC)란?
C#은 자동 메모리 관리를 위해 가비지 컬렉터를 사용한다
더 이상 참조되지 않는 객체를 자동으로 감지하고 제거하여 메모리를 회수한다
이 과정에서 게임성능에 영향을 줄 수 있다.
가비지 컬렉터 (Garbage Collector)
시스템에서 메모리 관리를 담당하는 실제 프로그램 구성요소
- 메모리 관리를 수행하는 프로그램 또는 프로그램의 한 부분
- 가비지 컬렉션을 수행하는 주체
- 시스템의 구성 요소
가비지 컬렉션 (Garbage Collection)
프로그램이 수행하는 메모리 정리 작업
- 가비지 컬렉터가 수행하는 작업/프로세스 자체
- 불필요한 메모리를 찾고 해제하는 행위
- 작업/프로세스의 개념
쉽게 비유하자면
가비지 컬렉터 = 청소부
가비지 컬렉션 = 청소하는 행위
C#의 GC 작동 방식
1. 세대별 가비지 컬렉션 (Generational Garbage Collection)
- Generation 0 (Gen 0)
새로 생성된 객체들이 위치
가장 자주 GC가 발생
수명이 짧은 객체들이 주로 있음
- Generation 1 (Gen 1)
Gen 0에서 살아남은 객체들이 승격
Gen 0보다 덜 빈번하게 GC 발생
중간 수명의 객체들이 위치
- Generation 2 (Gen 2)
Gen 1에서 살아남은 객체들이 승격
가장 드물게 GC 발생
수명이 긴 객체들이 저장
2. Mark and Sweep 알고리즘
// Mark 단계 예시
class GC {
void Mark() {
// 1. GC Root에서 시작
// - 정적 필드
// - 스레드 스택의 로컬 변수
// - CPU 레지스터
// 2. 도달 가능한 모든 객체를 재귀적으로 마킹
foreach (object obj in roots) {
MarkAsReachable(obj);
}
}
// Sweep 단계
void Sweep() {
// 마킹되지 않은 객체들을 메모리에서 제거
// 연속된 빈 공간을 만들기 위해 압축
}
}
3. GC 작동 트리거 조건
- Gen 0 임계값 도달 시
- Gen 1 또는 Gen 2 임계값 도달 시
- 시스템 메모리 부족 시
- GC.Collect() 메서드 명시적 호출 시
4. Large Object Heap (LOH)
- 85KB 이상의 큰 객체들은 별도의 힙에 할당
- 세대별 GC와 다르게 관리됨
- 메모리 파편화 위험이 높음
// LOH 예시
byte[] largeArray = new byte[86000]; // LOH에 할당됨
5. GC 최적화 기법
public class GCOptimization
{
// 1. 메모리 압축 회피
private byte[] buffer = new byte[85 * 1024]; // LOH 회피
// 2. 재사용 가능한 객체 풀
private Stack<object> objectPool = new Stack<object>();
// 3. 가비지 생성 모니터링
public void MonitorGC()
{
long gen0Before = GC.CollectionCount(0);
long gen1Before = GC.CollectionCount(1);
long gen2Before = GC.CollectionCount(2);
// 작업 수행
Debug.Log($"Gen0 Collections: {GC.CollectionCount(0) - gen0Before}");
Debug.Log($"Gen1 Collections: {GC.CollectionCount(1) - gen1Before}");
Debug.Log($"Gen2 Collections: {GC.CollectionCount(2) - gen2Before}");
}
}
6. 백그라운드 GC
- 별도의 스레드에서 GC 작업 수행
- 애플리케이션 일시 정지 최소화
- Server GC와 Workstation GC로 구분
7. GC 모드
- Concurrent GC: 애플리케이션 실행과 동시에 GC 수행
- Background GC: 별도 스레드에서 GC 수행
- Foreground GC: 애플리케이션을 일시 정지하고 GC 수행
유니티 개발 시에는 특히 Gen 0 GC를 최소화하는 것이 중요하다
유니티 GC 특징
- Unity는 Boehm GC를 사용 (표준 .NET의 세대별 GC와 다름)
- Mark & Sweep 방식은 동일하지만 세대 관리 방식이 다름
- Unity 2019부터 Incremental GC 도입
// Unity의 GC 특성을 고려한 최적화 예시
public class UnityGCExample : MonoBehaviour
{
private void Start()
{
// Unity에서는 이런 방식의 할당이 특히 비효율적
for (int i = 0; i < 1000; i++)
{
var temp = new Vector3(i, i, i); // 매 프레임 GC 발생
}
}
// 대신 이렇게 사용
private Vector3[] vectors = new Vector3[1000]; // 미리 할당
private void BetterApproach()
{
for (int i = 0; i < 1000; i++)
{
vectors[i].Set(i, i, i); // GC 발생 없음
}
}
}
유니티 특화 GC 최적화
- IL2CPP 사용 시 GC 성능이 향상
- Unity Burst 컴파일러 사용으로 GC 회피 가능
- Unity Job System과 함께 사용 시 효율적
유니티 GC와 .NET의 차이점
- 세대별 수집이 없음
- 압축(Compaction)이 없음
- 병렬 GC 지원이 제한적
- 증분 GC는 선택적 기능
유니티 프로젝트 설정에서의 GC 관련 옵션
Project Settings에서 설정 가능
1. Incremental GC Enable/Disable
2. GC 타이밍 조절 (Quality Settings의 Time slice)
3. IL2CPP 사용 시 GC 옵션
유니티에서 GC가 발생하는 주요 상황
- 참조 타입(클래스) 객체 생성
- 박싱/언박싱 연산
- 문자열 연결 작업
- LINQ 사용
- foreach 루프 사용
- 람다식과 클로저 사용
가비지 생성 최소화 ( 메모리 최적화 )
1. 객체 풀링 사용
public class ObjectPool<T> where T : Component
{
private Queue<T> pool = new Queue<T>();
private T prefab;
public T Get() {
if (pool.Count == 0)
return GameObject.Instantiate(prefab);
return pool.Dequeue();
}
public void Return(T obj) {
pool.Enqueue(obj);
obj.gameObject.SetActive(false);
}
}
2. 문자열 처리 최적화
// 나쁜 예
string result = "";
for (int i = 0; i < 100; i++) {
result += i.ToString(); // 매번 새로운 문자열 객체 생성
}
// 좋은 예
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.Append(i);
}
string result = sb.ToString();
3. 캐싱 활용
// 나쁜 예
void Update() {
GameObject.Find("Player"); // 매 프레임마다 검색
}
// 좋은 예
private GameObject player;
void Start() {
player = GameObject.Find("Player"); // 한 번만 검색하고 저장
}
4.구조체 활용
// 클래스 대신 구조체 사용
public struct Position
{
public float x;
public float y;
public float z;
}
5. 제네릭 컬렉션 사용
// 나쁜 예
ArrayList list = new ArrayList(); // 박싱/언박싱 발생
// 좋은 예
List<int> list = new List<int>(); // 타입 안전성과 성능 향상
6. 값 타입과 참조 타입의 차이점
// 값 타입 (Stack에 직접 저장)
struct ValueData {
public int value;
}
// 참조 타입 (Heap에 저장되고 GC 대상)
class ReferenceData {
public int value;
}
void Example() {
ValueData v1 = new ValueData(); // GC 발생 안함
ReferenceData r1 = new ReferenceData(); // GC 대상
}
7. 유니티 이벤트와 델리게이트 최적화
// 나쁜 예 - 매번 새로운 델리게이트 할당
void Update() {
button.onClick.AddListener(() => DoSomething());
}
// 좋은 예 - 미리 할당된 메서드 사용
void Start() {
button.onClick.AddListener(DoSomething);
}
8. 프로퍼티 vs 필드
// 자동 프로퍼티는 컴파일러가 backing field를 생성
public Vector3 Position { get; set; } // 약간의 오버헤드 발생
// 직접 필드 사용이 더 효율적
public Vector3 position;
9. 유니티 API 사용 시 주의사항
// 피해야 할 메서드들 (GC 부하 발생)
GameObject.FindObjectsOfType<T>();
GameObject.Find();
GetComponents<T>();
10. 커스텀 Allocator 사용
public class CustomAllocator<T> where T : new()
{
private T[] items;
private Stack<int> freeIndices;
public CustomAllocator(int capacity)
{
items = new T[capacity];
freeIndices = new Stack<int>(capacity);
for (int i = capacity - 1; i >= 0; i--)
{
freeIndices.Push(i);
}
}
public T Allocate()
{
if (freeIndices.Count > 0)
{
int index = freeIndices.Pop();
items[index] = new T();
return items[index];
}
throw new System.OutOfMemoryException();
}
public void Free(T item)
{
int index = System.Array.IndexOf(items, item);
if (index != -1)
{
items[index] = default(T);
freeIndices.Push(index);
}
}
}
11. 메모리 파편화 방지
- 비슷한 크기의 객체들을 같이 할당하여 메모리 파편화 최소화
- 객체 풀의 크기를 미리 계산하여 동적 확장 방지
- 가능한 한 고정 크기의 배열 사용
12. 유니티 잡 시스템 활용
using Unity.Jobs;
using Unity.Collections;
public struct DataProcessJob : IJob
{
public NativeArray<float> data;
public void Execute()
{
// 값 타입과 NativeContainer를 사용하여 GC 없이 처리
for (int i = 0; i < data.Length; i++)
{
data[i] = ProcessData(data[i]);
}
}
}
13. 고급 프로파일링 기법
public class MemoryProfiler : MonoBehaviour
{
private System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
void Profile(string operation)
{
long beforeMem = System.GC.GetTotalMemory(false);
stopwatch.Reset();
stopwatch.Start();
// 프로파일링할 작업 수행
stopwatch.Stop();
long afterMem = System.GC.GetTotalMemory(false);
Debug.Log($"{operation}: Time={stopwatch.ElapsedMilliseconds}ms, " +
$"Memory Delta={(afterMem-beforeMem)/1024}KB");
}
}
14. 추가 최적화 팁
- Unity Profiler를 사용하여 GC 할당을 모니터링
- Update() 메서드 내에서 new 키워드 사용 최소화
- GetComponent 호출 결과를 캐싱
- 코루틴에서 WaitForSeconds 객체 재사용
실제 최적화가 필요한 시점
- 프로파일러에서 GC가 자주 발생하는 것이 확인될 때
- FPS 모니터링에서 주기적인 프레임 드랍이 발견될 때
- 메모리 사용량이 지속적으로 증가할 때
- 모바일 기기에서 발열이나 배터리 소모가 심할 때
- 로딩이나 씬 전환에서 끊김 현상이 발생할 때
- 대량의 객체를 생성/삭제하는 시스템
- 긴 시간 실행되는 게임에서 메모리 누수가 의심될 때
디버깅 및 모니터링
// 예시 코드
public class MemoryDebugger : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.M))
{
Debug.Log($"Total Memory: {System.GC.GetTotalMemory(false) / 1024 / 1024}MB");
System.GC.Collect();
}
}
}
과도한 최적화는 코드의 가독성과 유지보수성을 해칠 수 있다
그렇기에 실제 문제가 있는 부분을 찾아내어 선택적으로
적용하는 것이 좋을 수도 있다
가비지 컬렉션 최소화와 메모리 최적화
이 두가지는 상당히 겹치는 부분이 많다
메모리 최적화 방법
- 메모리를 효율적으로 사용
- 전반적인 메모리 사용량 감소
- 더 나은 성능을 위한 코드 구조 개선
가비지 생성 최소화 방법
- 불필요한 객체 생성을 줄임
- 재사용 가능한 객체 활용
- 임시 객체 생성 회피
가비지 컬렉션 최소화
가비지 컬렉터가 청소하는 작업을 최소화
1. GC.Collect() 호출 타이밍 조절
public class GCController : MonoBehaviour
{
void Start()
{
// 로딩이 끝난 직후나 씬 전환 시점에 GC 실행
StartCoroutine(CollectGCAtOptimalTime());
}
IEnumerator CollectGCAtOptimalTime()
{
// 중요한 작업이 없는 시점까지 대기
yield return new WaitForSeconds(1f);
System.GC.Collect();
}
}
2. 증분 가비지 컬렉션(Incremental GC) 설정
- Project Settings에서 설정 가능
- GC를 여러 프레임에 걸쳐 분산 실행
- 한 번에 큰 멈춤 현상 방지
3. 가비지 컬렉션이 발생하는 임계값 조정
public class GCThresholdManager
{
void AdjustGCThreshold()
{
// GCSettings를 통한 설정
// Unity에서는 제한적으로만 가능
System.Runtime.GCSettings.LargeObjectHeapCompactionMode =
System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;
}
}
4. GC 실행 시점 최적화
public class LoadingManager : MonoBehaviour
{
void LoadLevel()
{
// 큰 리소스를 로드하기 전에 GC 실행
System.GC.Collect();
Resources.Load("LevelData");
// 로드 완료 후 다시 한번 GC 실행
System.GC.Collect();
}
}
5. 프로파일링을 통한 GC 타이밍 모니터링
public class GCProfiler : MonoBehaviour
{
void Update()
{
// GC 발생 시점과 영향 모니터링
Debug.Log($"Gen 0 collections: {System.GC.CollectionCount(0)}");
Debug.Log($"Gen 1 collections: {System.GC.CollectionCount(1)}");
Debug.Log($"Gen 2 collections: {System.GC.CollectionCount(2)}");
}
}
가비지 생성 최소화 방법과 함께 사용하면
보다 효과적인 메모리 관리가 가능할 것 같다
반응형
'유니티 > 문법' 카테고리의 다른 글
[Unity] 유니티 C#) Event ( 이벤트 ), Action ( 액션 ) (0) | 2025.01.08 |
---|---|
[Unity] 유니티 C#) Delegate ( 델리게이트 ), Anonymous Method (익명 메서드) ,Lambda ( 람다 ) (0) | 2025.01.07 |
[Unity] 유니티 C#) ObjectPool ( 오브젝트풀링 ) (2) | 2025.01.03 |
[Unity] 유니티 C#) ScriptableObject ( 스크립터블오브젝트 ) (1) | 2025.01.02 |
[Unity] 유니티 C#) 코루틴(Coroutine) (3) | 2024.12.27 |