dotnet-counters

1 개의 포스트

.NET 지속적 프로파일러: 예외 및 락 경합 (새 탭에서 열림)

Datadog의 .NET 컨티뉴어스 프로파일러는 애플리케이션의 성능에 보이지 않는 영향을 미치는 예외(Exception)와 락 경합(Lock Contention)을 정밀하게 추적합니다. 단순히 발생 횟수를 세는 것을 넘어, 로우 레벨 CLR 콜백과 메타데이터 분석을 통해 예외 메시지와 락 유지 시간 등 구체적인 컨텍스트를 제공하여 성능 병목의 원인을 정확히 파악하도록 돕습니다. 이를 통해 개발자는 무분별한 예외 발생으로 인한 CPU 낭비와 병렬 알고리즘의 지연 시간을 효과적으로 최적화할 수 있습니다. **예외 발생 데이터 수집 및 타입 분석** * 예외가 던져질 때 CLR은 `ICorProfilerCallback::ExceptionThrown`을 호출하며, 프로파일러는 이를 통해 예외 객체의 `ObjectID`를 획득합니다. * `ICorProfilerInfo::GetClassFromObject`를 사용하여 예외 인스턴스의 `ClassID`를 구하고, 이를 `FrameStore`와 연동하여 클래스 이름을 확인하고 캐싱합니다. * 예외 처리는 `catch` 및 `finally` 블록의 실행을 보장하기 위해 런타임에서 많은 CPU 사이클을 소모하므로, 발생 지점과 타입별 통계를 파악하는 것이 중요합니다. **System.Exception 메시지 추출을 위한 메타데이터 탐색** * 예외의 세부 내용을 파악하기 위해 `_message` 필드의 값을 읽어야 하며, 이를 위해서는 해당 필드의 정확한 메모리 오프셋을 알아야 합니다. * .NET 버전(Framework 또는 Core)에 따라 `mscorlib`나 `System.Private.CoreLib` 모듈을 식별한 후, `IMetaDataImport`를 통해 `System.Exception` 클래스의 메타데이터 토큰을 찾습니다. * `ICorProfilerInfo2::GetClassLayout`을 호출하여 클래스의 필드 레이아웃 정보를 얻고, `_message` 필드의 `COR_FIELD_OFFSET`을 계산하여 문자열 버퍼의 위치를 특정합니다. **락 경합의 지속 시간 및 원인 식별** * .NET 런타임은 `Monitor.Enter` 등을 사용하는 락 패턴에 대해 `ContentionStart`와 `ContentionStop` 이벤트를 발생시킵니다. * .NET Framework와 같이 이벤트 자체에서 지속 시간을 제공하지 않는 경우, 프로파일러가 직접 스레드별로 시작 시점의 타임스탬프를 관리하여 대기 시간을 계산합니다. * .NET 8부터는 `ContentionStart` 이벤트에 락의 `ObjectID`와 현재 락을 점유 중인 스레드 정보가 포함되어, 어떤 스레드가 다른 스레드를 대기하게 만드는지 구체적으로 가시화할 수 있습니다. **EventPipe를 통한 효율적인 실시간 이벤트 모니터링** * .NET 5 이상에서는 `ICorProfilerCallback10::EventPipeEventDelivered` 메서드를 통해 CLR 이벤트를 동기적으로 수신할 수 있습니다. * `ClrEventParser` 클래스는 이벤트 페이로드에서 ID와 키워드를 기반으로 필요한 필드만 추출하여 성능 부하를 최소화합니다. * 이러한 메커니즘을 통해 애플리케이션 실행에 거의 영향을 주지 않으면서도(Negligible impact) 상세한 프로파일링 데이터를 확보합니다. 성능 최적화를 위해서는 `Parse` 대신 `TryParse`를 사용하여 불필요한 예외 비용을 줄이고, 프로파일러가 제공하는 락 지속 시간 데이터를 바탕으로 과도한 동기화 구문을 개선하는 실용적인 접근이 필요합니다.