GC TEST

C# 2022. 3. 8. 09:43

 

 

 

# README

## 참고 자료

* [메모리 할당 감소](https://docs.microsoft.com/ko-kr/windows/uwp/debug-test-perf/improve-garbage-collection-performance#reduce-memory-allocations)
* [가비지 컬렉션 (GarbageCollection)](https://nomad-programmer.tistory.com/235)
* [EventPipe](https://docs.microsoft.com/ko-kr/dotnet/core/diagnostics/eventpipe)
* [EventPipe 의 Event 종류](https://docs.microsoft.com/ko-kr/dotnet/fundamentals/diagnostics/runtime-garbage-collection-events#gcsuspendeebegin_v1-event)
* [.Net 5 의 GC 성능 향상](https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-5/#gc)
* [8 Techniques to Avoid GC Pressure and Improve Performance in C# .NET](https://michaelscodingspot.com/avoid-gc-pressure/)
* [Spying on .NET Garbage Collector with .NET Core EventPipes](https://medium.com/criteo-engineering/spying-on-net-garbage-collector-with-net-core-eventpipes-9f2a986d5705)
* [FlatSharp Pool 미지원 및 대체 방안](https://github.com/jamescourtney/FlatSharp/issues/263)
* [진단 클라이언트 라이브러리](https://docs.microsoft.com/ko-kr/dotnet/core/diagnostics/diagnostics-client-library)
* [가비지 컬렉션 ETW 이벤트](https://docs.microsoft.com/ko-kr/dotnet/framework/performance/garbage-collection-etw-events)
* [What Is Faster In C#: A Struct Or A Class?](https://mdfarragher.medium.com/whats-faster-in-c-a-struct-or-a-class-99e4761a7b76)
  * When you’re storing more than 30–40 bytes of data, use a class.
  * When you are storing reference types, use a class.
  * When you are storing up to a few thousand instances, use a class.
  * When you list is long-lived, use a class.
  * In all other cases, use structs.

## GC

* Ref.
  * "https://docs.microsoft.com/ko-kr/dotnet/standard/garbage-collection/fundamentals#conditions-for-a-garbage-collection"

* GC 실행 조건
  * OS 시스템 메모리 부족
  * 관리되는 힙 메모리 임계값 초과
  * GC.Collect 호출

* GC gen(세대)
  * 각 세대에 메모리가 부족한 경우 해당 세대에 대해 GC 가 발생한다.
  * 0 gen(임시 세대) : 짧은 수명 개체
  * 1 gen(임시 세대) : 짧은 수명 개체. 0 과 1의 버퍼
  * 2 gen : 긴 수명 개체
  * 3 gen : LOH(대량 개체 힙). 2 세대에서 GC 되기도한다.

* GC 가 수행하는 작업
  * 활성 개체 목록 작성
  * 개체의 참조 업데이트
  * 비활성 개체 회수 및 생존 개체 압축. LOH 는 압축하지 않음.
  * GC 중인 스레드 외에는 모두 정지 시킴.
  * 서버 GC(백그라운드 GC) 는 multi thread.

## Test

Struct, Class 생성하여 GC 상태를 확인한다.

### 요약 정보

* Struct 사용
  * Struct 는 Stack 을 사용하고 Class 는 Heap 을 사용한다.
    * Struct 는 GC 이 대상이 아니다.
    * .net core 3.1 default stack size : 1536 KB
  * 휘발성이 강한 경우 사용하면 좋다.
  * 배열이나 container 에 넣는 경우 Heap 에 상주하게 된다.
  * 이런 경우 pool 을 만들어 사용 한다.
* struct 사용시 stack 을 사용하므로 GC 대상에서 제외된다.

#### 기타 정보

* Gen 0 -> Gen 1 -> Gen 2 로 객체를 밀어 올리는 방법
  * GC.Collect() API 를 사용하여 이동을 유도할 수 있다.
  * 인위적으로 이동시킬수 있는 API 는 없다.

    ```csharp
    // GC.Collect 를 호출하여 밀어올린다.
    Object obj = new object();
    Console.WriteLine($"generation: {GC.GetGeneration(obj)}");  // generation: 0
    GC.Collect();
    Console.WriteLine($"generation: {GC.GetGeneration(obj)}");  // generation: 1
    GC.Collect();
    Console.WriteLine($"generation: {GC.GetGeneration(obj)}");  // generation: 2
    GC.Collect();
    Console.WriteLine($"generation: {GC.GetGeneration(obj)}");  // generation: 2
    ```

* 인위적으로 객체를 GC 대상에서 제외 방법은 없다.
* GC 모니터링방법
  * EventPipe 를 사용하거나 dotnet cmd tool 이 있다.
  * .net core 3.1 프로세스는 은 "성능 모니터"의 ".NET CLR Memory" 카운터에 표시되지 않음

### 테스트 방법

* [EventPipe](https://docs.microsoft.com/ko-kr/dotnet/core/diagnostics/eventpipe) 로 GC event 로깅
* struct, class 생성 방식 변경하며 GC 로그 확인
* 인스턴스는 3억개 생성

### 테스트 코드

```csharp

// 배열 생성
public static void AllocIntArray()
{
    for (int i = 0; i < Count; i++)
    {
        int[] x = new int[100];
    }
}

// class instance 생성
public static void AllocClass()
{
    for (int i = 0; i < Count; i++)
    {
        AllocClass inst = new AllocClass()
        {
            Number = i,
        };
    }
}

// struct instance 생성
public static void AllocStruct()
{
    for (int i = 0; i < Count; i++)
    {
        // GC 이벤트 자체가 일어나지 않음
        AllocStruct inst = new AllocStruct()
        {
            Number = i,
        };
    }
}

// list 에 class instance 삽입
public static void AllocClassInList()
{
    List<AllocClass> list = new List<AllocClass>();

    for (int i = 0; i < Count; i++)
    {
        AllocClass inst = new AllocClass()
        {
            Number = i,
        };

        list.Add(inst);
    }
    list.Clear();
}

// list 에 struct instance 삽입
public static void AllocStructInList()
{
    List<AllocStruct> list = new List<AllocStruct>();

    for (int i = 0; i < Count; i++)
    {
        AllocStruct inst = new AllocStruct()
        {
            Number = i,
        };

        list.Add(inst);
    }

    list.Clear();
}
```

### 테스트 결과

| |AllocIntArray|AllocClass|AllocStruct|AllocClassInList|AllocStructInList|
|---|---|---|---|---|---|
|Alloc 소요시간|7,536 msec|890 msec|65 msec|9,855 msec|1,192 msec|
|GC 발생 횟수|900|50|0|6|0|
|GC 소요 시간|326 msec|14 msec|0 msec|11,717 msec|0 msec|
|FragmentedBytes|3 MiB|7.5 MiB|0|69.5 MiB|0|
|HeapSizeBytes|4.8 MiB|8.6 MiB|0|12,357.8 MiB|0|
|테스트 후 GC.Collect시 발생 횟수|3|1|0|2|1|
|테스트 후 GC.Collect시 소요 시간|20 msec|8 msec|0 msec|2562 msec|218 msec|

* [GCMemoryInfo 구조체](https://docs.microsoft.com/ko-kr/dotnet/api/system.gcmemoryinfo?view=netcore-3.1)
  * FragmentedBytes : 마지막 가비지 수집이 발생한 총 조각화를 가져옵니다. (heap 이 compact 되기전에 생존한 객체 사이에 빈공간의 총 크기)
  * HeapSizeBytes : 마지막 가비지 컬렉션이 발생 한 총 힙 크기 (바이트)입니다.

 

 

 

 

'C#' 카테고리의 다른 글

c# method attribute 의 값을 해당 메소드에서 가져오기  (0) 2022.03.15
c# HttpClient  (0) 2022.03.15
c# channel  (0) 2022.03.15
object pool, object type pool  (0) 2022.03.11
GC EventPipe 모니터링  (0) 2022.03.08
C# .net core 빌드 및 powershell 전송  (0) 2022.03.01
c# stack size 확인  (0) 2022.02.24
숫자 범위 추출 및 확장  (0) 2021.11.03