포스트

캐시Cache의 Spatial Locality

이번에는 캐시에 대해서 간단히 실습해보겠습니다. 물론 캐시에 대해 아는 것도 멀티쓰레드 프로그래밍과 관련이 있습니다. 멀티쓰레드 프로그래밍에서는 코드가 캐시를 얼마나 효율적으로 잘 사용하는지 여부에 따라서 그 성능의 차이가 싱글쓰레드 프로그래밍보다 커진다고 합니다. 그렇다면 캐시란 무엇일까요? 지난 포스팅에서 소개했던 AMD사의 CPU를 다시 보겠습니다.

76MB의** L2+L3캐시**가 있다고 소개하고 있습니다.

캐시는 CPU가 가지고 있는 메모리인데, 우리가 일반적으로 더 잘 접해왔던 컴퓨터의 메모리부품(RAM)의 역할을 CPU에서도 일부분 맡고 있는데 그 부품을 캐시라고 합니다. 그 용량이 매우 적어서 별도의 RAM 부품은 당연히 대체할 수 없지만 도대체 왜 필요할까요? 캐시가 필요한 이유는 컴퓨터에서 CPU와 RAM의 물리적인 거리가 있기 때문입니다. 와닿게 하기 위해서 사진을 보여드리겠습니다. 아래 사진은 컴퓨터에서 CPU와 RAM이 체결되는 메인보드입니다. 두 부품이 물리적으로 구별되는 부품이므로 거리가 존재할 수 밖에 없습니다.

그래서 모든 데이터를 다 RAM으로 옮겨서 기억하도록 하게 하기보다는, 잠깐 동안만 기억하면 좋을 정보는 RAM까지 전달할 필요 없이 CPU내에서 단기간 동안만 기억하도록 한다면 더 좋은 성능을 가질 수 있습니다.

캐시가 동작하는 원리에는 두 가지 원리가 있다고 합니다.

  1. 방금 처리한 정보와 시간적으로 가까운 정보는 캐시에 저장한다. (Temporal Locality)
  2. 방금 처리한 정보와 공간적으로 가까운 정보는 캐시에 저장한다. (Spatial Locality)

그러면 이제 Temporal Locality를 고려해서 캐시가 잘 사용되도록 코딩을 해보고, 그렇지 않은 경우와 비교를 해봅시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;
using System.Data;
using System.Threading;

namespace ServerCore
{
    internal class Program
    {
        static void Main(string[] args)
        {
            int[,] arr = new int[10000, 10000];

            {
                // 10000 * 10000 배열
                // [첫번째][두번째][세번째][네번째]...10000개  [][][][]...10000개  [][][][]...10000개  ...
                // 인접해 있는 데이터에 접근할 때는 캐시로 처리하기 때문에 빠르다.
                long now = DateTime.Now.Ticks;
                for (int y &#x3D; 0; y < 10000; y++)
                    for (int x &#x3D; 0; x < 10000; x++)
                        arr[y, x] &#x3D; 1;
                long end &#x3D; DateTime.Now.Ticks;
                Console.WriteLine($"(y, x) 순서 걸린 시간 {end - now}");
            }

            {
                // 10000 * 10000 배열
                // [첫번째][][][]...10000개  [두번째][][][]...10000개  [세번째][][][]...10000개  [네번째][][][]...10000개...
                // 인접하지 않은 데이터에 접근하므로 방금 전 예시보다 캐시를 덜 사용하여서 덜 빠르다.
                long now &#x3D; DateTime.Now.Ticks;
                for (int y &#x3D; 0; y < 10000; y++)
                    for (int x &#x3D; 0; x < 10000; x++)
                        arr[x, y] &#x3D; 1;
                long end &#x3D; DateTime.Now.Ticks;
                Console.WriteLine($"(x, y) 순서 걸린 시간 {end - now}");

            }


        }
    }
}

결과는 어떤가요? 첫 번째 경우가 더 빠른 것을 볼 수 있습니다. 그 이유는 첫 번째 경우는 배열에 접근할 때 가깝게 위치한 정보에 접근하기 때문입니다. 가까운 정보는 캐시에 저장되어 있었어서 더 빠르게 로드한 것입니다. 멀티쓰레드 프로그래밍에서는 이러한 차이가 더 커질 수 있다고 합니다.


이 글은 인프런의 '[C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버' '캐시 이론' 강의를 참고하여 정리 목적으로 작성하였습니다. 감사합니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.