C#

C# 기초부터 고급까지 Chapter 3.1. Span , Memory , Unsafe – 퍼포먼스 챙기는 C# 코드

Juan_ 2025. 5. 4. 18:52
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

항목SpanMemory
저장 위치스택힙 가능
사용 시점동기비동기 포함
구조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;
}
방법평균 시간메모리
배열500nsGC 힙 사용
Span300ns스택 사용

➡️ Span<T>는 작은 크기일수록 압도적
➡️ GC도 안 타서 불필요한 메모리 사용 줄일 수 있음


✅ 정리 요약

항목요약
Span스택 기반, 고성능 슬라이스 참조
Memory힙 저장 가능, 비동기 호환
stackalloc스택에 직접 배열 생성
unsafe포인터 직접 다루는 고성능 저수준 기술
실무 활용파싱, 버퍼 처리, 메모리 민감 코드에 핵심

📢 다음 챕터 예고 🎓

다음 챕터주제
Chapter 3.2Reflection & Dynamic Type – 런타임 메타프로그래밍
728x90