C#, Native Safe Buffer 구현
Native Safe Buffer를 구현하기 위해 ref struct 와 NativeMemory 클래스를 사용한다. ref struct는 주로 실제 메모리 주소(포인터)를 들고 있는 Span<T>을 안전하게 만들기 위해 존재하고 그 메모리를 안전하고 빠르게 다루는 규칙이라고 할 수 있다.
NativeMemory 클래스는 가비지 컬렉터(GC)의 관리를 받지 않는 네이티브 힙 메모리를 직접 할당하고 관리할 수 있게 해주는 도구인데, 기존의 Marshal.AllocHGlobal이나 stdole 등을 이용하던 방식보다 성능이 뛰어나고 C 언어의 malloc, free와 유사한 인터페이스를 제공한다.
다만 GC가 메모리를 치워주지 않으므로 Free호출이 필수이며, 잘못된 주소에 접근하면 프로그램이 즉시 Access Violation 될 수 있다.
메모리 할당 주체
- 배열로부터 가져올 때:
Span<byte> span = new byte[1024];: 이것은 결국 Managed 배열이고. GC가 관리하며, 대용량일 경우 GC 부하(LOH 등)가 발생한다. - 스택으로부터 가져올 때:
Span<byte> span = stackalloc byte[1024];: GC 부하가 없고 빠르지만, 크기 제한이 엄격하며 보통 1MB가 넘어가면 StackOverflowException 발생 - 네이티브 메모리로부터 가져올 때:
NativeMemory.Alloc(...count...): 가장 빠르고 크기 제한도 없지만, 수동으로 해제(Free) 하지 않으면 메모리 누수(Memory Leak)가 발생
stackalloc으로 만든 Span은 그 함수가 끝나면 사라지지만 네이티브 메모리로 만든 NativeSafeBuffer는 (비록 ref struct라 제약은 있지만) Dispose를 호출하기 전까지는 메모리가 안정적으로 유지된다.
| 비교 항목 | new byte[] (Span) | stackalloc (Span) | NativeSafeBuffer |
|---|---|---|---|
| 관리 주체 | 가비지 컬렉터 (GC) | 스택 (Stack) | OS (Native) |
| 해제 시점 | GC가 한가할 때 | 함수 종료 시 즉시 | Dispose 호출 시 즉시 |
| 크기 제한 | 힙 메모리만큼 | 매우 작음 (1MB) | RAM 용량만큼 |
| 안전성 | 매우 안전함 | 빠르지만 위험함 | 수동 해제 필수 (using) |