> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 코어 간 메모리 비용

> 코어 간 메모리 비용

## 개요

[XBOX One과 XBOX Series X|S 비교](https://learn.microsoft.com/gaming/gdk/docs/gdk-dev/console-dev/overviews/system/XboxOne-vs-ProjectScarlett)에서 다룬 기반 내용을 확장하여, 이 문서에서는 프로세서 코어 간에 공유되는 메모리에 대한 다양한 읽기/쓰기 작업과 관련된 성능 문제와 비용을 다룹니다. XBOX One 계열과 XBOX Series 콘솔 CPU의 아키텍처, 기능 및 성능에 대한 개괄이 필요하다면 위에서 언급한 문서를 먼저 살펴보시길 권장합니다.

[소개](#introduction)

[공유 데이터](#shared-data)

[데이터 공유의 비용](#costs-of-data-sharing)

[권장 사항](#recommendations)

[부록: 코드](#appendix-code)

## 소개

<a id="Introduction" />

***

[XBOX One과 XBOX Series X|S 비교](https://learn.microsoft.com/gaming/gdk/docs/gdk-dev/console-dev/overviews/system/XboxOne-vs-ProjectScarlett)에서는 L1 캐시 미스와 관련된 비용을 설명합니다. L1 캐시 미스가 발생하는 가장 흔한 패턴 중 하나는 CPU 코어 간에 공유되는 주소에 쓰기를 수행하는 것입니다. 예를 들어 여러 스레드 간에 공유되는 데이터가 있습니다. 이 문서의 목적은 코어 간에 쓰기 가능한 데이터를 공유할 때 발생하는 패널티에 대한 상대적 수치를 제공하는 것입니다.

테스트에서 얻은 주요 결론은 코어와 스레드 간에 데이터를 공유하기보다 각 코어 내부로 읽기/쓰기 작업을 국한하는 것이 바람직하다는 것입니다. 여러 코어 간에 데이터를 공유하면 코드 처리가 빨라지기는커녕 느려집니다.
XBOX One 계열은 **Jaguar** 프로세서를 사용하고 XBOX Series 콘솔은 **Hercules** 프로세서를 사용합니다. 두 프로세서 모두 [MOESI protocol - Wikipedia](https://en.wikipedia.org/wiki/MOESI_protocol)을 사용하여 강한 메모리 모델과 캐시 일관성을 유지합니다. 각 캐시 라인은 다섯 가지 상태 중 하나에 있을 수 있습니다.

* Modified
  * 이 프로세서만 캐시 라인의 유효한 복사본을 가지고 있으며 변경을 수행했습니다.
  * 캐시 라인이 메모리와 일치하지 않습니다.
* Owned
  * 이 프로세서만 유효한 복사본을 가지고 있으며 변경을 수행했습니다.
  * 다른 프로세서는 읽기 전용 복사본을 가질 수 있습니다.
* Exclusive
  * 이 프로세서만 캐시 라인의 복사본을 가지고 있습니다.
  * 캐시 라인의 내용이 메모리와 일치합니다.
* Shared
  * 이 프로세서는 캐시 라인의 여러 복사본 중 하나를 가지고 있습니다.
  * 변경이 이루어졌다면 다른 프로세서가 Owned 상태로 가지고 있을 수 있습니다.
* Invalid
  * 이 캐시 라인은 유효하지 않으며 접근 전에 가져와야 합니다.

AMD 구현에 대한 자세한 내용은 [AMD Developer Central](https://developer.amd.com/resources/developer-guides-manuals/)에서 제공하는 AMD64 Architecture Programmer's Manual Volume 2: System Programming을 참조하세요.

## 공유 데이터

<a id="shared-data" />

***

데이터 공유는 여러 가지 방식으로 일어날 수 있습니다. 좀 더 명확한 예로는 구조체의 참조 카운트와 같이 변수를 직접 공유하는 경우입니다. 예를 들어 함수 호출의 매개변수로 shared\_ptr를 통해 각 스레드가 복사본을 만들면 데이터가 쉽게 수정될 수 있으며, 이 작업만으로도 리소스 경합이 발생합니다.

또 다른 종류의 데이터 공유인 거짓 공유(false sharing)는 감지하기가 더 어렵습니다. 거짓 공유는 두 메모리 주소가 동일한 캐시 라인으로 매핑될 때 발생합니다. Jaguar와 Hercules 프로세서 모두 L1 캐시 라인은 64바이트 크기이며 64바이트로 정렬되어 있으므로, 동일한 64바이트 블록 내의 두 정수는 공유되는 것으로 간주됩니다. 예를 들어 다음 두 데이터 구조를 살펴보세요.

```cpp theme={null}
class OuterClassSlow
{
    struct InnerStruct
    {
        uint64_t threadOne;
        uint64_t threadTwo;
        uint64_t threadThree;
        uint64_t threadFour;
    };

    InnerStruct dataArray[1024];
};

class OuterClassFast
{
    uint64_t dataArrayThreadOne[1024];
    uint64_t dataArrayThreadTwo[1024];
    uint64_t dataArrayThreadThree[1024];
    uint64_t dataArrayThreadFour[1024];
};
```

threadOne, threadTwo, threadThree, threadFour 데이터는 스레드별로 고유합니다. 스레드 1은 threadOne 데이터에서만 작동하고, 스레드 2는 threadTwo 데이터에서만 작동하는 식입니다. 이 두 데이터 구조를 비교했을 때, 표준 쓰기 작업만으로 멀티스레드 처리로 전환할 경우 OuterClassSlow가 OuterClassFast보다 최대 20배 느리게 동작할 수 있습니다. 이는 OuterClassSlow의 데이터가 거짓 공유의 결과로 모두 동일한 캐시 라인을 공유하기 때문입니다. 메모리에서 서로 인접해 있기 때문입니다. 이는 구조체의 배열(AoS)과 배열의 구조체(SoA) 간 차이를 보여주는 예입니다.

<a id="costs-of-data-sharing" />

## 데이터 공유의 비용

***

### 테스트 프로파일

테스트 프로파일은 다음 기준을 따랐습니다.

* 테스트한 명령어는 원시 읽기(raw read), 원시 쓰기(raw write), 원자적 로드(atomic load), 원자적 저장(atomic store), 원자적 비교 후 저장(Compare and Store, CAS) 작업입니다. 각 명령어 집합에 사용된 코드는 [부록: 코드](#appendix-code)에서 제공됩니다.
* 테스트는 모든 스레드가 공유하는 8바이트 메모리 위치(공유 캐시 라인) 또는 각 스레드가 고유한 8바이트 메모리 위치(고유 캐시 라인)를 사용하여 수행되었습니다.
* 4만 개의 작업이 타이트 루프에서 실행되었습니다.
* 옵티마이저는 루프 언롤링을 비활성화하도록 크기 최소화로 설정되었습니다.
* 총 100회 실행이 수행되었으며, 아래 표에는 모든 실행의 중앙값이 표시됩니다.

### 단일 코어 테스트

기준선을 잡기 위해 단일 코어에서 각 작업을 테스트하는 것부터 시작했습니다.

\| 작업              | XBOX One | XBOX One X | XBOX Series S - 3.4 GHz | XBOX Series X|S - 3.6 GHz | XBOX Series X - 3.8 GHz |
\|--------------|----------:|----------:|----------:|----------:|----------:|
\| **Raw Read**   | 68.78 us  |   52.34 us  |   11.81 us  |   11.16 us  |   10.56 us  |
\| **Atomic Load**  | 68.78 us  | 52.34 us  |   11.81 us  |   11.16 us  |   10.56 us  |
\| **Raw Write**  | 114.61 us  | 104.64 us  |   11.81 us  |   11.16 us  |   10.56 us  |
\| **Atomic Store**  | 369.55 us  | 278.99 us  |   207.11 us  |   195.61 us  |   185.31 us  |
\| **Atomic CAS**  | 668.79 us  | 509.02 us  |   203.43 us  |   192.13 us  |   182.03 us  |

첫 번째는 Jaguar 프로세서와 Hercules 프로세서 사이의 타이밍 차이입니다. Hercules에서 성능이 최대 4배 향상됩니다. 이는 더 높은 클럭 속도, 더 높은 대역폭, 그리고 동시에 진행 중인 더 많은 메모리 작업에 대한 지원의 조합에 기인합니다.

40,000회의 연속 읽기와 40,000회의 원자적 로드를 비교하면 시간 차이가 전혀 없습니다. 이는 CPU의 강한 메모리 모델과 MOESI 프로토콜 때문입니다. 컴파일러는 두 작업 모두에 대해 동일한 명령어를 생성할 수 있습니다.

이 테스트의 원시 쓰기 및 원자적 저장 작업은 메모리 값을 증가시키려고 시도합니다. 이 테스트에서 원시 쓰기와 원자적 저장 사이의 비용이 두 배로 증가합니다. 원자적 저장이 *xchg* 명령어를 사용하기 때문인데, 이 명령어는 암묵적으로 *lock* 접두어를 포함합니다. 이로 인해 결과가 캐시로 플러시될 때까지 프로세서가 정지될 가능성이 있는데, *lock* 접두어를 사용하는 명령어 간에는 작업 재정렬이 불가능하기 때문입니다. 원시 쓰기는 mov 명령어를 사용합니다. 이는 프로세서를 정지시키지 않으며, mov 명령어에 대해 작업 재정렬이 자유롭게 허용됩니다.

이제 원자적 비교 후 저장(CAS) 작업으로 넘어갑니다. 이 테스트의 작업은 원시 쓰기 및 원자적 저장 테스트와 유사하게 메모리의 값을 증가시키려고 시도합니다. 이 테스트에서는 원자적 저장 테스트와 비교 가능하도록 *cmpxchg* 명령어에 *lock* 접두어가 사용되었습니다. Jaguar 프로세서에서는 CAS 작업이 비교 때문에 앞서 언급한 *xchg*보다 비용이 더 많이 듭니다. 그러나 Hercules 프로세서에는 개선된 구현이 있어 비용이 원자적 저장과 비슷하게 유지됩니다.

### 다중 코어 테스트

다중 코어 테스트는 모두 여러 코어 구성에서 두 개의 스레드를 사용했습니다.

* 서로 다른 물리적 코어에 있지만 동일한 클러스터에 있는 두 스레드.
* 서로 다른 물리적 코어이면서 서로 다른 클러스터에 있는 두 스레드.
* SMT가 활성화된 경우, 단일 물리적 프로세서의 두 논리 코어에서 실행되는 두 스레드.

각 테스트에 대해 두 번의 서로 다른 실행을 수행했습니다. 첫 번째 실행에서는 두 스레드가 동일한 *uint64\_t* 주소를 공유했습니다. 두 번째 실행에서는 각 스레드가 캐시 라인을 공유하지 않는 자신만의 고유한 *uint64\_t* 주소를 가졌습니다. 이렇게 한 이유는 프로세서 간에 캐시 라인이 공유될 때의 성능 페널티를 더 쉽게 확인할 수 있도록 하기 위함입니다. 각 테스트에서 스레드는 동시에 시작되었으며, 타이밍은 루프 반복에서만 측정되었습니다. 컨텍스트 스위치 가능성을 최대한 낮추기 위해 스레드는 높은 우선순위로 설정되었습니다. 각 테스트마다 100회 실행이 수행되었으며, 아래 표에는 중앙값 결과가 표시됩니다.

아래 수치는 모두 기본 단일 코어 테스트를 기준으로 한 상대값이며, 여러 코어에 걸쳐 실행할 때 해당 작업이 얼마나 더 비싸졌는지를 나타냅니다.

#### 원시 읽기(Raw read) / 원자적 로드(Atomic Load)

이는 메모리 위치에서의 단순한 읽기입니다. 데이터를 변경하는 것이 없기 때문에 스레드 간 경합이 없으며, 각 스레드는 자신의 L1 캐시에 데이터의 자체 복사본을 가지고 있습니다. 그 결과 공유 데이터를 읽는 시간과 고유 데이터를 읽는 시간이 동일합니다. 강한 메모리 모델 덕분에 컴파일러는 원자적 로드에 대해 일반 읽기와 동일한 코드를 생성할 수 있습니다. 즉, 각 작업의 소요 시간이 동일합니다.

\| 테스트              | XBOX One | XBOX One X | XBOX Series X|S - SMT | XBOX Series X|S - no SMT |
\|--------------|----------:|----------:|----------:|----------:|----------:|
\| **Single Core**   | 1.00  |    1.00 |    1.00 |    1.00 |
\| **Same Physical Shared**  | N/A  | N/A |    1.97 |    N/A |
\| **Same Cluster Shared**  |  1.00 | 1.00 |    1.00 |    1.00 |
\| **Cross Cluster Shared**  | 1.00 |  1.00 |    1.00 |    1.00 |
\| **Same Physical Unique**  | N/A | N/A |    1.97 |    N/A |
\| **Same Cluster Unique**  | 1.00 | 1.00 |    1.00 |    1.00 |
\| **Cross Cluster Unique**  | 1.00 | 1.00 |    1.00 |    1.00 |

이 표에서 얻을 수 있는 두 가지 주요 시사점이 있습니다.

첫째, 여러 코어가 동일한 위치에서 읽기를 수행하더라도 메모리에서 읽기만 하는 경우 성능에 영향이 없다는 점입니다. 각 프로세서는 자신의 캐시에 유효한 복사본을 가지고 있으며, 여러 프로세서가 복사본을 가지므로 캐시 라인은 Shared 상태에 있습니다.

둘째, 두 스레드가 동일한 물리적 코어에서 실행되는 구성에서 시간이 두 배가 된다는 점입니다. SMT가 활성화되면 코어의 리소스가 두 스레드 사이에 공유됩니다. 이는 종종 단일 스레드가 사용 가능한 모든 리소스를 활용하지 못하므로 성능 향상으로 이어질 수 있습니다. 그러나 이 테스트는 Load/Store 유닛과 L1 캐시를 지배하는 매우 타이트한 루프입니다. 단일 스레드는 Load/Store 유닛의 모든 슬롯을 사용할 수 있으므로, 두 스레드는 라운드 로빈 방식으로 공유해야 합니다. 결과적으로 각 스레드가 두 배 오래 걸리지만, 코어에서 두 배의 작업이 수행됩니다. 전체적으로 코어는 같은 시간에 동일한 양의 작업을 수행합니다.

#### 원시 쓰기(Raw write)

코어 간에 공유되는 메모리에 대한 단순한 쓰기는 단일 코어가 비공유 메모리 위치에 쓰는 것보다 훨씬 느립니다. 쓰기 작업은 코어가 캐시 라인을 갱신하고 다른 코어의 복사본을 무효화하게 만듭니다.

이 특정 테스트는 공유 메모리의 값을 증가시키는 것입니다. 즉, 코어의 캐시에 유효한 복사본이 없으면 메모리 또는 해당 주소에 마지막으로 쓴 코어로부터 데이터를 요청해야 합니다. 이는 두 코어 간에 핑퐁 효과를 만들 수 있는데, 한 코어가 데이터를 갱신하면 두 번째 코어가 첫 번째 코어로부터 데이터를 읽어 갱신하고, 그러면 첫 번째 코어가 다시 갱신하기 전에 그 코어로부터 데이터를 읽어야 하는 식입니다. 이는 테스트 루프의 각 반복마다 앞뒤로 계속됩니다.

핑퐁 효과의 오버헤드는 이 표에서 확인할 수 있습니다.

\| 테스트              | XBOX One | XBOX One X | XBOX Series X|S - SMT | XBOX Series X|S - no SMT |
\|--------------|----------:|----------:|----------:|----------:|----------:|
\| **Single Core**   | 1.00 |   1.00  |   1.00  |   1.00 |
\| **Same Physical Shared**  | N/A  | N/A |   10.69  |   N/A  |
\| **Same Cluster Shared**  | 10.54 | 8.73 |   24.36  |    23.59 |
\| **Cross Cluster Shared**  | 13.61 | 12.78 |   18.89  |    23.57 |
\| **Same Physical Unique**  | N/A | N/A |   1.98  |   N/A  |
\| **Same Cluster Unique**  |  1.05 | 0.93 |   1.03  |    1.03 |
\| **Cross Cluster Unique**  | 1.01  | 0.84 |   1.04  |    1.03 |

이 표에서 몇 가지 시사점을 얻을 수 있습니다.

첫째, Jaguar 프로세서와 Hercules 프로세서 간의 성능에 대한 상대적 영향의 차이입니다. Hercules에서는 캐시 라인 공유 비용이 Jaguar보다 더 큽니다. 이 경우 Jaguar에서 다른 코어로부터 데이터를 요청하는 데 17 사이클이 드는 반면, Hercules에서 동일한 작업은 90 사이클이 듭니다. 이는 Hercules의 더 깊은 캐시, 특히 Jaguar에는 없는 L3 캐시의 존재 때문입니다.

둘째, 공유 메모리 위치와 고유 위치를 사용할 때의 차이입니다. 이 경우 작업은 현재 값의 증가로, 이는 읽기/수정/쓰기 작업입니다. 읽기 작업은 해당 메모리 위치에 가장 최근에 쓴 코어에 의해 처리되어야 합니다. 그 코어가 다른 코어라면 읽기 비용이 극적으로 높아집니다. 표에서 볼 수 있듯이 최대 25배까지 비쌀 수 있습니다.

셋째, SMT가 활성화된 상태에서 두 스레드가 단일 물리적 코어를 공유할 때의 상대적인 차이가 더 작다는 점입니다. 갱신된 캐시 라인을 다른 코어에서 가져올 필요가 없고 이미 로컬에 있기 때문입니다. 그러나 store-to-load 포워딩은 적용될 수 없습니다. 스레드 A가 값을 갱신하면 스레드 B가 값을 사용하기 전에 캐시로 플러시되어야 합니다.

넷째, 두 스레드가 동일한 물리적 코어에서 고유 메모리를 대상으로 실행되는 구성에서 시간이 두 배가 된다는 점입니다. SMT가 활성화되면 코어의 리소스가 두 스레드 사이에 공유됩니다. 이는 종종 단일 스레드가 사용 가능한 모든 리소스를 활용하지 못하므로 성능 향상으로 이어질 수 있습니다. 그러나 이 테스트는 Load/Store 유닛과 L1 캐시를 지배하는 매우 타이트한 루프입니다. 단일 스레드는 Load/Store 유닛의 모든 슬롯을 사용할 수 있으므로, 두 스레드는 라운드 로빈 방식으로 공유해야 합니다. 결과적으로 각 스레드가 두 배 오래 걸리지만, 코어에서 두 배의 작업이 수행됩니다. 전체적으로 코어는 같은 시간에 동일한 양의 작업을 수행합니다.

#### 원자적 저장(Atomic store)

원자적 저장은 *xchg* 명령어를 사용하며, 이 명령어는 암묵적으로 *lock* 플래그를 알립니다. *xchg* 명령어는 해당 메모리 주소를 읽은 다음 쓰기가 필요합니다. 이 동안 캐시 라인이 잠기며, 다른 코어가 해당 캐시 라인에 접근하지 못하도록 막습니다. 이는 *xchg* 명령어가 데이터를 캐시에 쓸 때까지 대기 중인 작업을 정지시킬 수 있습니다. 또 다른 비용은 코어가 *xchg* 명령어 전반에 걸쳐 작업을 재정렬할 수 없다는 점입니다. 마지막 비용은 다른 코어가 데이터를 수정한 경우입니다. 이 경우 해당 코어로부터 데이터를 가져와야 합니다. 다른 코어들은 공유 캐시 라인에서 작업할 때 이 전체 시퀀스 동안 대기해야 합니다.

Raw Write 테스트와 마찬가지로 Atomic Store 테스트도 유사한 핑퐁 효과가 있을 수 있지만, 이 경우 더 두드러지며 심지어 단일 코어 테스트에도 영향을 미칩니다.
프로세서는 루프의 한 반복 이상으로 사변적 실행을 할 수 없습니다. 스레드 A가 *xchg* 명령어를 수행하는 동안 스레드 B는 접근을 기다립니다. 스레드 A가 완료되자마자 스레드 B는 계속 실행할 수 있으며, 스레드 A가 루프의 다음 반복에서 접근을 요청하기 전에 즉시 복사본을 요청합니다.

이 표는 단일 코어가 작업을 수행하는 것과 비교한 *xchg* 명령어의 상대적 비용을 보여줍니다.

\| 테스트              | XBOX One | XBOX One X | XBOX Series X|S - SMT | XBOX Series X|S - no SMT |
\|--------------|----------:|----------:|----------:|----------:|----------:|
\| **Single Core**   | 1.00 |   1.00  |   1.00  |   1.00 |
\| **Same Physical Shared**  | N/A  | N/A |    2.38 |   N/A  |
\| **Same Cluster Shared**  | 6.84 | 6.85 |    4.17 |    4.04 |
\| **Cross Cluster Shared**  | 10.64 | 13.40 |    3.72 |    3.56 |
\| **Same Physical Unique**  | N/A | N/A |    0.98 |   N/A  |
\| **Same Cluster Unique**  | 1.00 | 1.00 |    1.00 |    1.00 |
\| **Cross Cluster Unique**  | 1.01 | 1.00 |    1.00 |    1.00  |

이 표의 데이터에서 두 가지 시사점이 있습니다.

공유 주소를 사용할 때의 상대적 시간은 원시 쓰기에 비해 원자적 저장의 경우 그리 극단적이지 않습니다. 이 테스트의 비용 상당 부분이 이미 단일 코어 테스트에서 지불되었기 때문입니다. 그러나 Atomic Store의 전체 비용은 여전히 Hercules에서 원시 쓰기보다 2배에서 4배 더 비싸며, Jaguar에서는 최대 13배 더 비쌉니다.

둘째, Hercules는 *lock* 작업에 대해 개선된 구현을 가지고 있으며, 이 경우 동일 클러스터와 크로스 클러스터 테스트의 소요 시간이 대략 비슷합니다. Jaguar 프로세서는 데이터가 클러스터 간에 공유되고 *lock* 작업이 사용될 때 최대 두 배까지 비쌀 수 있습니다.

#### 원자적 비교 후 저장(Atomic Compare and Store, CAS)

이 테스트의 원자적 비교 후 저장(CAS) 작업은 *lock* 접두어와 함께 *cmpxchg* 명령어를 사용합니다. 즉, 작업이 수행되는 동안 캐시 라인이 잠기고 다른 프로세서가 접근할 수 없습니다. 이는 Atomic Store 테스트에서 *xchg* 명령어가 작동하는 방식과 동일합니다.

이 테스트에서 CAS 작업은 Atomic Store 작업과 동일한 연산을 수행합니다. 공유이든 고유이든 메모리의 값을 증가시키려고 시도합니다. 유일한 차이점은 CAS 작업이 지정된 세 번째 값과 같을 때만 메모리 위치에 쓴다는 것입니다.

\| 테스트              | XBOX One | XBOX One X | XBOX Series X|S - SMT | XBOX Series X|S - no SMT |
\|--------------|----------:|----------:|----------:|----------:|----------:|
\| **Single Core**   | 1.00 |   1.00  |   1.00  |   1.00 |
\| **Same Physical Shared**  | N/A  | N/A |    2.91 |   N/A  |
\| **Same Cluster Shared**  |  4.32 | 4.32 |    4.01 |    4.24 |
\| **Cross Cluster Shared**  |  10.12 | 14.80 |    3.52 |    3.45 |
\| **Same Physical Unique**  | N/A | N/A |    0.91 |   N/A  |
\| **Same Cluster Unique**  |  1.00 | 1.00 |    1.00 |    1.00 |
\| **Cross Cluster Unique**  |  1.00 | 1.00 |    1.00 |    1.00 |

Atomic Store와 Atomic Compare and Store 테스트 모두 *lock* 접두어를 사용하기 때문에 상대적 비용은 동일하며 그 이유도 같습니다.
공유 주소를 사용하는 단일 스레드와 멀티스레드 작업 간의 상대적 시간이 극단적이지 않은 이유는 이 테스트 비용의 상당 부분이 이미 단일 코어 테스트에서 지불되었기 때문입니다. 그러나 CAS의 전체 비용은 데이터가 공유될 때 Hercules에서 여전히 2배에서 4배 더 비싸고, Jaguar에서는 최대 14배 더 비쌉니다.

둘째, Hercules는 *lock* 작업에 대해 개선된 구현을 가지고 있으며, 이 경우 동일 클러스터와 크로스 클러스터 테스트의 소요 시간이 대략 비슷합니다. Jaguar 프로세서는 데이터가 클러스터 간에 공유되고 *lock* 작업이 사용될 때 최대 두 배까지 비쌀 수 있습니다.

<a id="recommendations" />

## 권장 사항

***

코어 간에 쓰기 가능한 데이터를 공유하지 않도록 따를 수 있는 몇 가지 핵심 패턴이 있습니다.

* 데이터 구조: 각 스레드가 고유한 구조체에서 작업하도록 구조체의 배열(AoS)에서 배열의 구조체(SoA)로 전환합니다.
* 읽기 전용 데이터를 읽기/쓰기 데이터와 분리합니다.
* 패딩: 캐시 라인 크기(64바이트)의 배수만큼 구조체에 패딩을 추가할 수 있습니다. 이렇게 하면 각 스레드가 하나의 구조체에서 작업할 경우 거짓 공유가 제거됩니다.
* 참조 카운팅(shared\_ptr, weak\_ptr 등): 이들은 객체의 모든 인스턴스 간에 공유되는 단일 참조 카운트 변수를 가지고 있습니다.
* 데이터의 소유권을 명확히 하세요. 데이터가 다른 스레드로 처리를 위해 넘겨지면 이제 그 스레드가 데이터를 소유합니다. 해당 스레드가 데이터 처리를 끝낼 때까지 그 데이터에 대해 아무 것도 하지 않도록 합니다.
* 공유 작업 큐: 특히 작업이 작을 때 여러 스레드가 공유하는 하나의 작업 큐를 사용하지 않도록 합니다. 대신 각 스레드에 고유한 작업 큐를 두는 방법을 고려하세요. 스레드 간 부하를 균형 있게 유지하기 위해 작업 훔치기(work stealing) 알고리즘을 사용할 수 있습니다.

<a id="appendix-code" />

## 부록: 코드

***

### 원시 읽기(Raw read)

```
for (uint64_t j = 0; j < params.iterations; j++)
{
    fred += *buffer + j;
}

mov         rcx,qword ptr [r8]  
add         rcx,rdx  
inc         rdx  
add         rdi,rcx  
cmp         rdx,r9  
jb          PerfRun::WorkerThreadRead+0D4h (07FF756A2E094h)  
```

### 원자적 로드(Atomic load)

```
for (uint64_t j = 0; j < params.iterations; j++)
{
    fred += buffer->load() + j;
}

mov         rax,qword ptr [rsi]  
add         rax,rcx  
inc         rcx  
add         rdi,rax  
cmp         rcx,rdx  
jb          PerfRun::WorkerThreadAtomicRead+0CFh (07FF756A2E4EFh)  
```

### 원시 쓰기(Raw write)

```
for (uint64_t j = 0; j < params.iterations; j++)
{
    *buffer = *buffer + 1;
}

mov         rcx,qword ptr [r8]  
inc         rcx  
mov         qword ptr [r8],rcx  
sub         rdx,1  
jne         PerfRun::WorkerThreadWrite+0D2h (07FF756A2E212h)  
```

### 원자적 저장(Atomic store)

```
for (uint64_t j = 0; j < params.iterations; j++)
{
    buffer->store(buffer->load() + 1);
}

mov         rcx,qword ptr [rdi]  
inc         rcx  
xchg        rcx,qword ptr [rdi]  
sub         rdx,1  
jne         PerfRun::WorkerThreadAtomicWrite+0CAh (07FF756A2E38Ah)  
```

### 원자적 CAS

```
for (uint64_t j = 0; j < params.iterations; j++)
{
    uint64_t temp = buffer->load();
    buffer->compare_exchange_strong(temp, temp + 1);
}

mov         rax,qword ptr [rdi]  
lea         rcx,[rax+1]  
lock cmpxchg qword ptr [rdi],rcx  
sub         rdx,1  
jne         PerfRun::WorkerThreadAtomicCAS+0CDh (07FF756A2E68Dh)
```


## Related topics

- [XBOX GDK 타이틀을 위한 메모리 시스템](/ko/build/console-features/memory/index.md)
- [PlayFab Services SDK](/ko/services/playfab/sdks/c/index.md)
- [Microsoft GDK Game OS 메모리 관리자 사용](/ko/build/console-features/memory/system-memory-working.md)
- [메모리 후킹](/ko/services/playfab/sdks/c/memory.md)
- [ID3D12Heap](/ko/reference/graphics/d3d12/interfaces/id3d12heap/id3d12heap_public.md)
