마이크로소프트가 .NET 11의 성능 개선 사항을 정리한 연례 장문 포스트를 공개했다. 매년 그렇듯 내용은 방대하지만, 올해의 줄기는 세 가지다. 조건부 탈출 분석(CEA)의 패턴 확장, NativeAOT의 인터페이스 디스패치 개선, 그리고 런타임 수준으로 내려간 async.
요약하면 경계 검사가 사라지고, 할당이 일어나지 않고, 락이 걸리지 않고, 루프가 더 적은 사이클로 돌고, 비교가 상수로 접히고, 중복 검사가 루프 밖으로 끌어올려지고, 명령이 하나로 합쳐지고, 시스템콜이 우회되고, 배열 복사가 SIMD로 넘어간다.
나는 이 시리즈를 매년 기다리는 부류다. 프레임워크 성능 개선 글이 재미있는 이유는, 대부분의 항목이 내가 코드를 한 줄도 안 바꿔도 적용된다는 데 있다. 런타임을 올리는 것만으로 벤치마크가 좋아진다. 물론 공짜처럼 보이는 것에는 늘 조건이 붙는다. 어떤 개선은 특정 코드 모양에서만 발동하고, 그 모양을 알면 코드를 그쪽으로 유도할 수 있다.
탈출 분석 확장 — 할당이 사라지는 조건
탈출 분석은 객체가 메서드 밖으로 새지 않는다는 걸 컴파일러가 증명하면, 힙 대신 스택에 두거나 아예 제거하는 최적화다. .NET 10에서 도입된 조건부 탈출 분석(CEA)은 "어떤 경로에서만 탈출하는" 경우까지 다뤘고, .NET 11은 그 패턴 집합을 넓혔다.
실무에서 가장 크게 체감되는 곳은 열거자(enumerator)다. foreach는 구조체 열거자를 쓰더라도 인터페이스로 박싱되는 순간 할당이 생긴다. 이 할당이 제거되면 뜨거운 루프에서 GC 압력이 눈에 띄게 준다.
// 할당이 사라질 수 있는 모양
static int Sum(List<int> items)
{
int total = 0;
foreach (var x in items) // List<T>.Enumerator (구조체)
total += x;
return total;
}
// 할당이 남기 쉬운 모양 — 인터페이스로 받으면 박싱 경로
static int Sum(IEnumerable<int> items)
{
int total = 0;
foreach (var x in items) // IEnumerator<int> 박싱
total += x;
return total;
}
// 팁: 뜨거운 경로에서는 구체 타입으로 받고,
// 범용 API는 오버로드를 나눠 두는 편이 낫다.
MemoryDiagnoser로 할당 바이트 수를 직접 확인하는 게 유일하게 신뢰할 방법이다. "이론상 제거될 것"은 성능 보고서에 쓸 문장이 아니다.NativeAOT — 인터페이스 디스패치와 바이너리 크기
NativeAOT는 인터페이스 디스패치에 공유 디스패치 헬퍼를 쓰도록 바뀌었다. 호출 지점마다 생성되던 코드가 줄어 바이너리 크기가 작아지고, 인터페이스 호출이 많은 워크로드의 처리량이 올라간다. 컨테이너 이미지 크기와 콜드 스타트가 중요한 서버리스·CLI 도구에 직접적인 이득이다.
런타임 네이티브 async — 스택 추적이 읽힌다
C#의 async/await는 오랫동안 컴파일러가 상태 기계를 생성하는 방식이었다. 동작은 훌륭했지만 두 가지 대가가 있었다. 예외 스택 추적에 컴파일러 생성 프레임이 잔뜩 끼고, 상태 기계 객체 할당이 뒤따랐다. 런타임 수준 async는 이 둘을 개선한다. 디버깅 경험이 좋아지는 쪽이 체감상 더 크다.
서버 애플리케이션에서 예외 로그를 읽어 본 사람이라면 MoveNext로 가득한 스택을 기억할 것이다. 원인 프레임을 찾으려고 눈으로 스크롤하던 그 시간이 줄어든다.
업그레이드 판단 — 무엇을 재고 옮길 것인가
출처
- .NET Blog, "Performance Improvements in .NET 11" — https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-11/
- Microsoft Learn, "What's new in .NET 11" — https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-11/overview
- Visual Studio Magazine, ".NET 11 Preview 5 Focuses on Performance, Productivity and Safer Code" — https://visualstudiomagazine.com/articles/2026/06/12/net-11-preview-5-focuses-on-performance-productivity-and-safer-code.aspx
자주 묻는 질문
.NET 11로 올리면 코드 수정 없이 빨라지나요?
상당 부분은 그렇습니다. JIT·런타임·라이브러리 수준의 개선은 재컴파일만으로 적용됩니다. 다만 개선 폭은 워크로드에 따라 크게 다르고, I/O 대기가 지배적인 서비스에서는 체감이 작을 수 있습니다. 반드시 자체 벤치마크로 확인하세요.
탈출 분석이 뭔가요?
객체가 생성된 메서드 밖으로 빠져나가지 않는다는 것을 컴파일러가 증명하면, 힙 할당을 스택 할당으로 바꾸거나 아예 제거하는 최적화입니다. .NET 11은 조건부 탈출 분석이 인식하는 패턴을 넓혀 열거자 같은 흔한 경우에서 할당을 더 많이 제거합니다.
할당이 줄었는지 어떻게 확인하나요?
BenchmarkDotNet에 MemoryDiagnoser를 붙이면 호출당 할당 바이트와 GC 수집 횟수를 볼 수 있습니다. 프로덕션에서는 Gen0 수집 빈도와 할당률 카운터를 추적하세요. 최적화 발동 여부는 코드 모양에 따라 달라지므로 측정이 유일한 확인 수단입니다.
NativeAOT를 꼭 써야 하나요?
콜드 스타트와 배포 크기가 중요한 경우에 유리합니다. 반면 리플렉션·런타임 코드 생성·플러그인 동적 로딩에 의존하는 애플리케이션은 전환 비용이 큽니다. CLI 도구나 소형 서비스부터 적용해 보고 확대하는 방식을 권합니다.
런타임 네이티브 async는 기존 async 코드와 호환되나요?
기존 async/await 코드는 그대로 동작합니다. 바뀌는 것은 내부 구현으로, 스택 추적이 간결해지고 오버헤드가 줄어드는 쪽의 개선입니다. 코드 작성 방식을 바꿀 필요는 없습니다.

댓글 0