6
C# 개발자 입장에서 **캐시 메모리(Cache Memory)**는 직접 구현하거나 제어하진 않지만, 성능 최적화와 관련된 중요한 개념입니다. 아래에 캐시 메모리의 동작 원리부터 정책, 매핑까지의 개념을 설명하고, 가능한 경우 C# 코드 또는 .NET의 관점에서 어떻게 반영되거나 유추되는지까지 설명하겠습니다.
🧠 캐시 메모리란?
CPU가 **주기억장치(RAM)**보다 빠른 속도로 데이터를 처리하기 위해 사용하는 고속 임시 저장 공간입니다.
C#에서는 직접 접근은 불가능하지만, 반복적인 배열 접근, 딕셔너리 캐시 구현, 웹 캐싱, JIT 최적화 등에 간접적으로 캐시의 영향을 받을 수 있습니다.
1. ⚙️ 캐시 메모리 동작 원리
- CPU가 어떤 주소의 데이터를 요구
- 캐시에 있으면 캐시 히트(hit) → 빠르게 반환
- 없으면 캐시 미스(miss) → RAM에서 읽고 캐시에 저장 후 반환
📌 C# 간접 예시:
int[] arr = Enumerable.Range(0, 100000).ToArray();
long sum = 0;
for (int i = 0; i < arr.Length; i++)
{
sum += arr[i]; // 연속된 접근 → 캐시 히트 ↑
}
arr
가 연속된 메모리로 할당되므로, 공간 지역성에 따라 CPU 캐시 히트율이 높아집니다.
2. 📂 캐시 메모리 유형
유형 | 설명 |
---|---|
L1 Cache (Level 1) | CPU Core에 가장 가까운 매우 빠른 캐시 (수십 KB) |
L2 Cache | L1보다 크고 느리며 여러 코어가 공유 가능 |
L3 Cache | CPU 전체 또는 여러 코어 간 공유되는 캐시 (MB 단위) |
🧩 C# 관점: 최적화 루프, 반복 참조, 구조체 정렬 등은 L1/L2 캐시에 영향을 줌
3. 📍 지역성(Locality)
종류 | 설명 |
---|---|
시간 지역성 | 최근 접근된 데이터가 다시 사용될 가능성 ↑ |
공간 지역성 | 접근한 데이터 근처의 데이터도 곧 사용할 가능성 ↑ |
📌 C# 예:
int[] arr = new int[100000];
for (int i = 0; i < arr.Length; i++)
arr[i] = i; // 공간 지역성↑, 연속된 메모리 접근
for (int i = 0; i < arr.Length; i += 1024)
arr[i] += 1; // 캐시 미스↑, 공간 지역성↓
4. 📌 캐시 교체 알고리즘
정책 | 설명 |
---|---|
LRU (Least Recently Used) | 가장 오래 안 쓴 항목 제거 |
FIFO (First In First Out) | 가장 먼저 들어온 항목 제거 |
LFU (Least Frequently Used) | 사용 빈도가 가장 낮은 항목 제거 |
📌 C# 사용자 캐시 구현 예 (LRU):
public class LRUCache<K, V>
{
private readonly int capacity;
private readonly Dictionary<K, LinkedListNode<(K key, V val)>> map;
private readonly LinkedList<(K key, V val)> list;
public LRUCache(int capacity)
{
this.capacity = capacity;
map = new Dictionary<K, LinkedListNode<(K, V)>>();
list = new LinkedList<(K, V)>();
}
public V Get(K key)
{
if (map.TryGetValue(key, out var node))
{
list.Remove(node);
list.AddFirst(node); // 최근 사용으로 이동
return node.Value.val;
}
return default!;
}
public void Put(K key, V value)
{
if (map.TryGetValue(key, out var node))
{
list.Remove(node);
}
else if (map.Count == capacity)
{
var last = list.Last!;
list.RemoveLast();
map.Remove(last.Value.key);
}
var newNode = new LinkedListNode<(K, V)>((key, value));
list.AddFirst(newNode);
map[key] = newNode;
}
}
5. ✏️ 쓰기 정책 (Write Policy)
정책 | 설명 |
---|---|
Write Through | 캐시와 RAM에 동시에 씀 (일관성 ↑, 속도 ↓) |
Write Back | 캐시에만 쓰고 나중에 RAM에 반영 (속도 ↑, 복잡성 ↑) |
🧩 C# 예: MemoryCache
vs WriteToFile
// Write Through 느낌
cache["user"] = userData;
File.WriteAllText("user.txt", JsonConvert.SerializeObject(userData));
// Write Back 느낌
cache["user"] = userData; // 디스크 반영은 이후 별도 처리
6. 📋 캐시 할당 정책
정책 | 설명 |
---|---|
Direct Mapping | 한 블록 → 하나의 캐시 라인 |
Fully Associative | 어떤 블록이든 아무 캐시 라인 가능 |
Set Associative | N개의 캐시 라인 중 한 곳만 선택 가능 (현실적인 절충) |
🧩 C#에서는 이런 하드웨어 캐시 할당을 직접 제어할 수는 없지만, 배열/구조체 정렬을 통해 캐시 친화적인 코드를 작성할 수 있음.
7. 🧼 캐시 플러시(Cache Flush)
- 캐시의 내용을 비우거나 주기억장치로 강제 저장하는 작업
- C#에서는
GC.Collect()
나 파일 버퍼 flush 등으로 간접 제어
📌 예:
// 버퍼된 파일 쓰기 강제 반영
using var stream = new FileStream("log.txt", FileMode.OpenOrCreate);
byte[] data = Encoding.UTF8.GetBytes("log data");
stream.Write(data, 0, data.Length);
stream.Flush(); // 캐시 플러시
✅ 요약 정리표
항목 | 설명 | C# 연관성 예시 |
---|---|---|
캐시 동작 원리 | 캐시 히트/미스를 통해 빠른 접근 | 배열 반복, 반복 계산 |
캐시 유형 | L1, L2, L3 | 최적화된 루프 구조 |
지역성 | 시간/공간 지역성 | 연속 배열 접근 |
교체 정책 | LRU, FIFO, LFU | 사용자 정의 캐시 구현 |
쓰기 정책 | Write Through, Write Back | MemoryCache와 파일 저장 비교 |
할당 정책 | Direct, Fully, Set Associative | 구조체 정렬, 연속성 고려 |
캐시 플러시 | 캐시 비우기 or 메모리 반영 | Flush(), GC.Collect() |
🔚 마무리
C#에서 캐시 메모리는 하드웨어적으로 직접 다루지 않지만, 코드 작성 방식이 CPU 캐시 효율성에 큰 영향을 미칩니다.
또한, 소프트웨어 수준의 캐싱은 MemoryCache
, Dictionary
, 또는 LRU 캐시 구현
을 통해 성능 최적화를 할 수 있습니다.