GC-동작-시간-확인

GC의 동작 시간을 코드에서 모니터링하려면 GC 이벤트가 발생한 시점을 기록하고, 해당 이벤트가 완료될 때까지의 시간을 측정해야 합니다. ETWEvent Tracing for Windows 이벤트를 활용하거나, 타이머를 사용하여 직접 측정하는 방법을 사용할 수 있습니다.

GC 강제 호출

GC.Collect()를 명시적으로 호출하고 시간 측정을 수행하여 GC의 소요 시간을 측정할 수 있습니다.

using System;
using System.Diagnostics;

class Program
{
    static void Main()
    {
        Stopwatch sw = new Stopwatch();

        // Full GC 강제 호출 및 시간 측정
        sw.Start();
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        sw.Stop();

        Console.WriteLine($"GC 수행 시간: {sw.ElapsedMilliseconds} ms");
    }
}
  • 이 방법은 실제 GC 작업에 의해 트리거된 시간이 아니라 강제 호출로 측정된 시간입니다.
  • 실시간 GC 모니터링에는 적합하지 않습니다.

ETW 사용

.NET 런타임에서 발생하는 GC 이벤트를 ETWEvent Tracing for Windows로 캡처하면 실시간으로 GC의 동작 시간을 측정할 수 있습니다.

GC 관련 ETW 이벤트

GCStart_Vx

  • GC 시작을 나타내는 이벤트
  • GC의 세대에 따라 표시다 달라짐.
    • 예 : Gen0 : GCStart_V0

GCEnd_Vx

  • GC 완료를 나타내는 이벤트

GCAllocationTick_Vx

  • 메모리 할당 시 발생하는 이벤트로, 작은 객체가 할당될 때마다 트리거
  • GC 자체보다는 메모리 사용량 추적에 더 유용

GCSuspendEEBegin

  • GC가 실행되기 전 Execution Engine(EE)을 일시 중단하는 이벤트.

GCSuspendEEEnd

  • GC 완료 후 Execution Engine(EE)을 다시 시작하는 이벤트.

GCFinalizersBeginGCFinalizersEnd

  • GC가 최종화 작업(Finalization)을 수행할 때 발생
  • 특정 객체가 Finalizer 큐에 있을 경우 트리거

GCHeapStats_Vx

  • GC 힙 상태를 제공하는 이벤트로, Gen0/Gen1/Gen2의 크기 및 메모리 사용량 모니터링

System.Diagnostics.Tracing 사용

.NET Core 3.0 이상에서 EventListener를 사용하여 GC 이벤트를 구독하고 동작 시간을 측정할 수 있습니다.

using System;
using System.Diagnostics.Tracing;
using System.Threading;

class Program
{
    static void Main()
    {
        var listener = new GCEventListener();
        Console.WriteLine("GC 모니터링 시작...");
        Thread.Sleep(Timeout.Infinite); // 프로그램을 유지
    }
}

class GCEventListener : EventListener
{
	public Stopwatch sw = new Stopwatch();
	int gcElapsed;
    private DateTime _gcStartTime;

    protected override void OnEventSourceCreated(EventSource eventSource)
    {
        // GC 관련 이벤트를 구독
        if (eventSource.Name == "Microsoft-Windows-DotNETRuntime")
        {
	        //0x1: GC 이벤트.
			//0x10: 기본 GC 이벤트를 활성화.
			//0x20: 특정 추가 GC 이벤트를 활성화.
             EnableEvents(eventSource, EventLevel.Verbose, (EventKeywords)(0x1 | 0x10 | 0x20));
        }
    }

    protected override void OnEventWritten(EventWrittenEventArgs eventData)
    {
        if (eventData.EventName!.Equals("GCSuspendEEBegin"))
		    sw.Restart();
		else if (eventData.EventName!.Equals("GCSuspendEEEnd"))
		{
		    gcElapsed = sw.GetElapsedMicroseconds();
		    Console.WriteLine($"GC : {gcElapsed}");
		}    
    }
}
  • 실시간 GC 모니터링 가능.
  • GC의 시작과 종료 시간을 이벤트 기반으로 측정.

.NET Diagnostic Tools 사용

.NET Core 3.0 이상에서는 DiagnosticListenerActivity를 사용하여 GC 관련 데이터를 모니터링할 수 있습니다.

using System;
using System.Diagnostics;
using System.Diagnostics.Tracing;

class Program
{
    static void Main()
    {
        var listener = new DiagnosticListener("GC_Monitor");
        GC.Collect();
        Console.ReadLine();
    }
}

class GCEventListener : EventListener
{
    private Stopwatch _stopwatch;

    protected override void OnEventSourceCreated(EventSource eventSource)
    {
        if (eventSource.Name == "System.Runtime")
        {
            EnableEvents(eventSource, EventLevel.Informational, EventKeywords.None);
        }
    }

    protected override void OnEventWritten(EventWrittenEventArgs eventData)
    {
        if (eventData.EventName == "GCStart")
        {
            _stopwatch = Stopwatch.StartNew();
            Console.WriteLine("GC 시작...");
        }
        else if (eventData.EventName == "GCEnd")
        {
            _stopwatch.Stop();
            Console.WriteLine($"GC 종료. 소요 시간: {_stopwatch.ElapsedMilliseconds} ms");
        }
    }
}

dotnet-counters 사용

설정 및 실행

  • .NET CLI에서 dotnet-counters 설치:
dotnet tool install --global dotnet-counters
  • 특정 프로세스의 GC 데이터를 실시간으로 모니터링:
dotnet-counters monitor --process-id <PID> System.Runtime
  • 주요 카운터
    • time-in-gc: GC에 소요된 총 시간 비율.
    • gen-0-gc-count, gen-1-gc-count, gen-2-gc-count: 각 세대의 GC 횟수.

비용 분석: ETW vs DiagnosticListener

두 방식 모두 비용이 발생하지만, 목적에 따라 비용 효율이 다를 수 있습니다.

ETW(EventListener)

장점

  • 효율적 설계: ETW는 운영 체제 수준에서 고성능으로 설계되었으며, 이벤트 추적 비용이 낮음.
  • 세부적인 데이터: GC와 관련된 이벤트(GCStart, GCEnd 등)를 상세히 제공.
  • 실시간 모니터링: GC 이벤트를 즉시 확인 가능.

비용

  • 이벤트 구독 비용:
    • EventListener는 이벤트를 수신할 때만 비용이 발생.
    • GC 이벤트 자체는 적은 양의 메모리를 사용하며, 이벤트가 자주 발생하지 않으면 부담이 크지 않음.
  • CPU 사용량:
    • 이벤트 수신 및 처리 로직에서 CPU 사용량이 증가할 수 있음.
    • 처리할 이벤트의 양이 많을 경우 CPU 사용량이 증가.
  • 메모리 사용량:
    • 이벤트 데이터를 저장하거나 분석할 경우 추가 메모리 소비.

적합한 사용 사례

  • 실시간 모니터링이 필요한 경우.
  • GC 이벤트만 필터링하여 확인하는 경우.
  • 전체 애플리케이션 성능에 큰 영향을 주지 않는 시나리오.

DiagnosticListener

장점

  • 구체적인 컨텍스트: 이벤트 외에도 진단 정보와 애플리케이션의 실행 컨텍스트를 함께 제공.
  • 다양한 진단 이벤트: GC 외에도 HTTP, 데이터베이스 쿼리 등 애플리케이션 이벤트를 폭넓게 모니터링 가능.

비용

  • 초기화 비용:
    • DiagnosticListener는 이벤트 외에도 다양한 진단 데이터를 제공하므로, EventListener에 비해 초기화 비용이 약간 더 높음.
  • 이벤트 처리 비용:
    • DiagnosticListener는 더 많은 범위의 데이터를 처리하므로 이벤트 처리 비용이 상대적으로 높음.
    • 이벤트가 많을 경우, 특히 실시간 분석이 추가된다면 성능 영향을 받을 수 있음.
  • 메모리 사용량:
    • 추가 데이터를 함께 처리하므로 이벤트당 메모리 소비량이 EventListener보다 더 높음.

적합한 사용 사례

  • 광범위한 애플리케이션 진단: GC뿐 아니라, HTTP 요청, 데이터베이스 쿼리 등의 상태를 함께 모니터링해야 할 때.
  • 운영 환경 진단: ETW 수준의 데이터를 제공하지 않아도 되는 상황.

비교: ETW vs DiagnosticListener

특징EventListenerDiagnosticListener
설계 목적운영 체제 수준 이벤트 추적.NET 애플리케이션 내부 진단 이벤트
CPU 사용량낮음중간
메모리 사용량적음상대적으로 많음
추적 가능 범위GC와 같은 런타임 이벤트 추적GC, HTTP 요청, 데이터베이스 등
이벤트 처리 효율성매우 효율적추가 데이터 처리로 인해 비용 증가
적합한 상황단일 목적(GC 이벤트 등)복합 목적(애플리케이션 상태 모니터링)