메모리 관련
CPU의 메모리 정렬
CPU가 메모리를 효율적으로 읽을 수 있도록 데이터를 특정 크기 단위로 정렬하는 것이다.
보통 2의 배수 byte단위로 정렬하는데, 1,2,4,8...로 정렬된다.
정렬되지 않은 데이터를 읽을려고 하면 컴파일러에 따라서는 바로 끊어버릴 수도 있다.
정렬되어있지 않다면 데이터들을 여러번에 걸쳐서 읽어들여야 하기 때문에 그만큼 cpu의 효율이 감소한다.
메모리가 정렬되면, 메모리 단위가 규칙적으로 바뀌게 되는데, 16바이트로 정렬된다면 마지막 주소값은 0이 된다.
0001 0000 = 16바이트/ 8바이트로 정렬되어 있다면 끝자리가 8아니면 0일 것이다. address boundary값이 정렬하고 싶은 값의 배수가 되지 않는다면 CPU가 그 주소값에 접근하는데 추가적인 비용이 들기도 하고, 하드웨어에서도 구현이 그렇게 되어있어서 그렇다.
캐시메모리 일관성(Cache Coherence)
- 캐시 메모리의 쓰기 정책
- Write-Through : 캐시메모리에 데이터가 write되는 시점에 데이터를 메인메모리에도 저장함.
- Write-Back : 캐시메모리에만 데이터를 write하다가, 새롭게 데이터를 교체해야 할 때, 데이터를 메인메모리에 저장함.
Write-Back이 더 속도가 빠르기 때문에 Write-Back이 주로 쓰임.
- 멀티코어에서의 캐시메모리 일관성 보장 프로토콜
요즘 코어는 멀티코어에 코어마다 L1, L2 캐시 및 공유 캐시인 L3캐시가 있다.
멀티쓰레드 환경에서는 코어마다의 캐시메모리가 동기화가 되어있지 않으면 사실상 사용할 수 없기 때문에 동기화를 보장해주어야 하는데, 보장 프로토콜이 여러개 있고 효율적인 프로토콜을 사용할수록 더욱 성능이 좋아진다.
프로세서간에 통로인 버스(bus)를 이용해서 각각의 캐시메모리들을 동기화를 가능하게 하는데, 정책으로 MSI, MESI 등이 있다.
MSI는 Modified, Shared, Invalid를 의미하며 캐시메모리가 데이터를 어떻게 가지고있냐는 flag이다.
Invalid는 현 캐시메모리가 Write 되었기 때문에 사용할 수 없는 상태를 의미하고,
Shared는 데이터를 Read만 하고 있거나 처음 데이터를 Read할 때의 상태이다.
Modified는 데이터를 Write하면 Shared에서 Modified로 상태가 변경되고 그 데이터를 가지고 있는 다른 프로세서에서의 캐시메모리 상태는 Invalid가 된다.
예시를 들어보면
1: P1: Read x
2: P1: Write x
3: P3: Read x
4: P3: Write x
5: P1: Read x
6: P3: Read x
7: P2: Read x
1. p1이 x를 읽기를 원함(bus로 신호를 보냄) -> 처음 접근임으로 캐시미스 -> p1이 x의 최신값을 메모리에서 가져와 저장하고 Shared상태
2. p1이 x를 쓰기를 원함 -> shared 상태인 나머지 캐시들한테 Invalid로 상태를 변경하라고 bus에 보냄 및 자신은 Modified상태로 변환
3. p3이 x를 읽기를 원함 -> p1이 신호를 받고 자신의 최신 데이터를 bus를 통해 전달 -> 메인메모리와 p1이 x를 저장 후 p1,p3가 x를 같은상태로 저장하고 있으므로 둘 모두 Shared상태가 됨.
4. p3이 x를 쓰기를 원함 -> p1이 Invalid, p3은 Modified상태가 됨.
5. p1이 x를 읽기를 원함 -> p1은 Invalid임으로 p3이 데이터를 메인메모리와 p1한테 전달 -> p1과 p3상태가 Shared상태가 됨.
6. p3이 x를 읽기를 원함 -> 현재 Shared상태임으로 bus로 신호를 보낼 필요가 없음 바로 읽기
7. p2이 x를 읽기를 원함 -> 캐시에 없으므로 bus로 신호를 보냄 -> 메모리,p1,p3 중 하나가 데이터를 전달 Shared 상태
MESI는 MSI에 Exclusive상태를 추가한 것으로 Shared상태는 혼자서 데이터를 점유해도 Shared상태임으로 다른 프로시저의 캐시에는 없고 자신만이 가지고 있는 상태일 때를 Exclusive로 정의하여 더 효율적인 동작을 하게 만드는 것이다.
False Sharing
CPU정책에 따라 캐시라인(cache line)이란게 존재하는데, 캐시라인 단위로 데이터를 가져오는데, 보통 32,64,128 byte이다.
만약 캐시라인이 64byte라면 변수 하나를 캐시에 저장해야한다면 그 단위를 주변 64byte까지 통째로 가져오는 것인데,
unt64 m; uint64 n;이 주변에 정의되어있으면 메모리 주소도 인접하게 할당될 것이기 때문에 캐시에 m이 저장되면 n도 같이 저장될 것이다.
즉 각 소켓(프로세서) 또는 코어가 동일한 캐시 라인 내의 서로 다른 위치의 데이터를 지역 캐시를 통해 참조할 때 거짓 공유가 발생한다. thread1이 m을 write하고, thread2가 n을 write할 때 m을 변경하면서 thread2의 프로시저에 캐시값이 동기화되야 하므로 위의 동기화 ex)MSI, MESI... 작업이 추가적인 overhead를 발생시킨다. m이 수정되면 같은 캐시라인에 있는 n쪽의 캐시라인이 무효화되고 그에 따라 캐시미스가 발생하게 되며 성능상으로 문제가 발생하게 된다.