728x90
📘 Chapter 3.1: Span , Memory , Unsafe – 퍼포먼스 챙기는 C# 코드
✅ 이 챕터에서 배울 것
- GC 힙이 아닌 스택 메모리 직접 다루기
- Span<T> / Memory<T> 차이와 쓰임
- stackalloc, ref struct의 역할
- unsafe 코드로 포인터 직접 다루는 법
- 안전성과 성능의 균형
- 실무 예제 및 성능 비교 (BenchmarkDotNet)
1️⃣ 왜 이걸 배워야 하나?
✅ C#은 편한 대신 느릴 수 있음
하지만 .NET도 진짜 성능 필요한 순간엔 치트키 제공한다!
- GC 힙 말고 스택 메모리 사용
- 배열 복사 대신 슬라이스 참조
- 포인터로 낮은 레벨 직접 접근
성능 민감한 작업 (파싱, 문자열 처리, 버퍼 연산)
전부 이 챕터에서 다루는 기술이 들어간다!
2️⃣ Span – 안전한 고성능 슬라이스
✅ Span 이란?
배열, 문자열, 메모리 블럭을 슬라이스처럼 참조하는 구조
→ 복사 없이, 빠르게, 스택에 위치 가능
Span<int> span = stackalloc int[5]; // 스택에 5개짜리 int 배열
span[0] = 100;
특징:
- ref struct → 힙에 저장 못함 (GC가 추적 불가)
- 배열, string, 포인터 모두 "참조"할 수 있음
- 메모리 복사 없이 "부분 접근" 가능
📦 슬라이스 예제
var array = new int[] { 1, 2, 3, 4, 5 };
Span<int> slice = array.AsSpan(1, 3); // {2, 3, 4}
slice[0] = 99;
Console.WriteLine(array[1]); // 99
→ 원본 배열을 복사한 게 아니라 참조만 했기 때문에
슬라이스 변경이 원본에 바로 반영됨!
3️⃣ Memory – 비동기에서도 사용 가능한 Span
항목 | Span | Memory |
---|---|---|
저장 위치 | 스택 | 힙 가능 |
사용 시점 | 동기 | 비동기 포함 |
구조 | ref struct | 일반 struct |
await에서 사용 | ❌ 못함 | ✅ 가능 |
📦 예제: Memory는 비동기 가능
public async Task ReadAsync(Memory<byte> buffer)
{
await stream.ReadAsync(buffer); // Span은 못 씀
}
➡️ Memory<T>는 내부적으로 Span<T>로 변환 가능 (.Span 프로퍼티 사용)
4️⃣ stackalloc – 스택에 배열 바로 생성하기
Span<byte> buffer = stackalloc byte[128];
✅ 특징:
- GC 힙에 올라가지 않음 → 할당/해제 빠름
- 제한된 용도 (작은 크기의 메모리만 적절)
🧠 힙 할당보다 훨씬 빠르지만,
스택 오버플로우 위험 있으므로 적절한 크기만 사용!
5️⃣ Unsafe – 포인터 직접 다루기
"안전한 C#? 잠깐만… 포인터 써도 된다니까요?"
unsafe
{
int x = 10;
int* ptr = &x;
Console.WriteLine(*ptr); // 10
}
✅ 특징:
- 반드시 unsafe 키워드와 /unsafe 옵션 필요
- 성능은 빠르지만, 실수는 위험함 (Null 참조, 메모리 오염 등)
📦 fixed 키워드 예시
unsafe
{
int[] arr = { 1, 2, 3 };
fixed (int* p = arr)
{
Console.WriteLine(*(p + 1)); // 2
}
}
→ 배열 주소를 고정시켜 포인터로 접근 가능하게 만듦
(안 그러면 GC가 이동시켜서 포인터 무효화 위험 있음)
6️⃣ 성능 비교 – BenchmarkDotNet 실험
[Benchmark]
public void WithSpan()
{
Span<int> span = stackalloc int[1000];
for (int i = 0; i < span.Length; i++)
span[i] = i;
}
[Benchmark]
public void WithArray()
{
int[] arr = new int[1000];
for (int i = 0; i < arr.Length; i++)
arr[i] = i;
}
방법 | 평균 시간 | 메모리 |
---|---|---|
배열 | 500ns | GC 힙 사용 |
Span | 300ns | 스택 사용 |
➡️ Span<T>는 작은 크기일수록 압도적
➡️ GC도 안 타서 불필요한 메모리 사용 줄일 수 있음
✅ 정리 요약
항목 | 요약 |
---|---|
Span | 스택 기반, 고성능 슬라이스 참조 |
Memory | 힙 저장 가능, 비동기 호환 |
stackalloc | 스택에 직접 배열 생성 |
unsafe | 포인터 직접 다루는 고성능 저수준 기술 |
실무 활용 | 파싱, 버퍼 처리, 메모리 민감 코드에 핵심 |
📢 다음 챕터 예고 🎓
다음 챕터 | 주제 |
---|---|
Chapter 3.2 | Reflection & Dynamic Type – 런타임 메타프로그래밍 |
728x90
'C#' 카테고리의 다른 글
C# 기초부터 고급까지 Chapter 3.3. Expression Tree로 컴파일 타임 코드 생성 (17) | 2025.05.05 |
---|---|
C# 기초부터 고급까지 Chapter 3.2. Reflection & Dynamic Type – 런타임 메타프로그래밍 (0) | 2025.05.05 |
C# 기초부터 고급까지 Chapter 2.10. 프로젝트 구조 패턴 – Layered vs Clean Architecture 소개 (0) | 2025.05.04 |
C# 기초부터 고급까지 Chapter 2.9. 인터페이스 활용 – 다형성 실전 예제 (5) | 2025.05.03 |
C# 기초부터 고급까지 Chapter 2.8.1: MSTest 완전정복 – 기본부터 실전 예제까지 (2) | 2025.05.02 |