컴파일러 최적화 알아보러가기
컴파일러 최적화
우리가 짠 코드를 컴파일러가 멋대로 튜닝을 해가지고 다른 결과물을 나오게 된다. 멀티 쓰레드에서는 독이 될수도 있다.
1. 하드웨어 최적화
01 Thread_1
static int x = 0;
static int r1 = 0;
static void Thread_1()
{
y = 1; // Store y;
r1 = x; // Load x (r1에 x 값 기입)
}
02 Thread_02
static int y = 0;
static int r2 = 0;
static void Thread_1()
{
y = 1; // Store y;
r1 = x; // Load x (r1에 x 값 기입)
}
Thread Pool에서 2개의 task가 있으니까 멀티쓰레드 환경
x = y = r1 = r2 = 0;
Task t1 = new Task(Thread_1);
Task t2 = new Task(Thread_2);
t1, t2가 끝날때까지 메인쓰레드에서 대기
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
03 경우의 수 조합
if (r1 == 0 && r2 == 0)
break;
-> X = 0, Y = 0의 경우의 수를 찾을 수 없다.
04 X = 0, Y = 0이 가능한 이유
하지만 출력을 해보면 빠져나온 것을 확인 할 수 있다.
이와 같은 결과가 나오는 이유는 하드웨어도 최적화 때문이다.
CPU같은 경우 일련의 명령어들을 수행할 때 명령어들이 서로 의존성이 없다고 판단될 경우 순서를 마음대로 뒤바꿀 수 있다. 순서를 마음대로 바꾸다가 이러한 경우 X = 0, Y = 0이 나오는 것이다.
05 문제점
싱글 쓰레드에서는 문제가 되지 않는다. R1 = X와 Y = 1이 진짜 둘이 연관성이 없기 때문이다.
하지만 멀티쓰레드에서 순서를 마음대로 바꾸면 우리가 예상한 로직이 꼬일수도 있다. 이를 해결하기 위해 메모리 베리어를 사용한다.
2. 메모리 베리어
현재 쓰레드를 실행 중인 프로세서는 MemoryBarrier()에 대한 호출 이전의 메모리 액세스 MemoryBarrier()에 대한 호출 이후의 메모리 액세스는 서로 접근할 수 없으므로 명령들을 마음대로 정렬 할 수 없다.
public static void MemoryBarrier ();
01 Memory Barrier종류
1) Full Memory Barrier (ASW MFENCE, C# Thread.MemoryBarrier) : Store/Load 둘다 막는다.
2) Store Memory Barrier (ASW SFENCE) : Store만 막는다.
3) Store Memory Barrier (ASW LFENCE) : Load만 막는다.
02 코드 재배치 억제
static void Thread_1()
{
y = 1;
//-------경계선--------//
Thread.MemoryBarrier();
r1 = x;
}
static void Thread_2()
{
x = 1;
//-------경계선--------//
Thread.MemoryBarrier();
r2 = y;
}
배리어 위의 코드가 배리어 아래의 코드와 순서가 바뀌어서 실행될 수 없도록 코드의 실행 순서를 보장해 준다.
그러므로 출력을 해보면 무한루프에 빠져 있는 것을 확인 할 수 있다.
03 가시성
예를 들어서 A직원이 주문을 받았을때 B직원은 A직원이 주문현황에 올리기 전까지 알지 못한다. 가시성은 A직원이 주문을 받은 것 자체를 다른 직원들이 바로 볼 수 있냐 없냐의 가시성을 얘기하는 것이다.
A직원이 자기 수첩에 적고 주문현황에 옮긴다. 그리고 두번째 직원이 주문현황의 정보를 자기 수첩에 적으면 실제 상황맞아떨어지게 업데이트 할 수 있다.
04 사용방법
Memory Barrier를 하나하나 명시적으로 입력하지 않아도 volatile과 lock등 같이 내부적으로 구현하고 있는 경우가 있다.
3. 대표적인 예시
int _answer;
bool _complete;
void A()
{
_answer = 123;
Thread.MemoryBarrier(); // Barrier 1
_complete = true;
Thread.MemoryBarrier(); // Barrier 2
}
void B()
{
Thread.MemoryBarrier(); // Barrier3
if(_complete)
{
Thread.MemoryBarrier(); // Barrier 4
Console.WriteLine(_answer);
}
}
만약 MemoryBarrier가 없었더라면 _answer = 123 , _complete가 true로 바뀌었음에도 불구하고 갱신이 안되는 문제가 발생했을 수도 있다.
MemoryBarrier 역할
Barrier1 : _answer = 123을 썼으므로 물을 내려주는 작업(갱신)을 통해서 가시성을 보장
Barrier2 : _complete을 썼으므로 물을 내려주는 작업(갱신)을 통해서 가시성을 보장
Barrier3 : _complete를 읽기전에 가시성이 보장해주기 위해서 물을 내려주는(갱신) 것이다.
Barrier4 : _answer가 최신정보가 맞는지 가져오기 전에 물을 내려주는(갱신) 것이다.
참고 : 본 내용은 MMORPG PART4 강의를 수강하여 작성하였습니다.
https://www.inflearn.com/course/%EC%9C%A0%EB%8B%88%ED%8B%B0-mmorpg-%EA%B0%9C%EB%B0%9C-part4
'cs공부 > 운영체제' 카테고리의 다른 글
운영체제 - 쓰레드(Thread), 기아상태,쓰레드풀(Thread Pool) (0) | 2023.04.28 |
---|---|
운영체제 - 문맥교환(Context switching), 프로세스의 상태 (0) | 2023.04.28 |
운영체제 - 프로세스(Process) (0) | 2023.04.28 |
C# - 운영체제 쓰레드 코드(ThreadPool, Task 등) (0) | 2023.04.27 |
운영체제 - 컴파일러 최적화(Release, volatile) (0) | 2023.04.27 |
댓글