레코드
C# 9.0 이후 레코드(Record)는 데이터 자체를 다루기 위해 설계되었다.
-
값 기반 비교: 일반 클래스는 참조 주소를 비교하지만, 레코드는 내부 데이터가 같으면 같은 객체로 간주한다.
-
불변성과 with 식: 데이터를 직접 수정하는 대신, with 키워드를 사용하여 기존 데이터를 복사하며 특정 값만 변경된 새 객체를 안전하게 생성한다.
-
간결한 선언: 한 줄의 코드로 생성자, 속성, Deconstruct 등을 모두 자동으로 생성한다.
할당 패러다임의 전환
최신 .NET은 메모리 할당을 최소화하기 위해 슬라이싱(Slicing) 개념을 적극 도입했다.
-
Span<
T>: 힙 할당 없이 메모리의 특정 영역(스택, 관리 힙, 비관리 메모리 등)을 안전하게 가리키는 구조체이다. 문자열을 자를 때 새로운 문자열 객체를 만들지 않고 원본의 특정 위치만 가리켜 할당량을 0바이트로 만든다. -
Memory<
T>: Span<T>과 비슷하지만 힙에 저장될 수 있어 async/await 키워드와 같은 비동기 메소드 사이에서 데이터를 전달할 때 사용한다. -
효과: 대규모 로그 파싱이나 네트워크 패킷 처리 시 가비지 컬렉터의 부담을 줄여준다.
고성능 비동기와 힙 최적화
힙 메모리 영역을 최적화하기 위한 수단은 다음과 같다.
-
ValueTask<
T>: Task<T>는 클래스이므로 호출 시마다 힙 할당이 발생하지만, ValueTask는 구조체로 구현되어 비동기 작업이 동기적으로 끝날 경우 힙 할당이 발생하지 않습니다. -
POH(Pinned Object Heap): .NET 5 이후 도입된 별도의 힙 영역으로 고정되는 객체들만 따로 모아 관리한다. 이를 통해 0-2세대 일반 힙의 메모리 압착(Compaction) 효율을 극대화한다.
-
NativeMemory: .NET 6 이후 C++ 언어처럼 직접 메모리를 할당 및 해제하여 가비지 컬렉터의 간섭을 배제한 임계 영역 설계가 가능해졌다.