KoreanFoodie's Study

[C++ 게임 서버] 1-13. CPU 파이프라인 (feat. 코드 재배치) 본문

Game Dev/Game Server

[C++ 게임 서버] 1-13. CPU 파이프라인 (feat. 코드 재배치)

GoldGiver 2023. 7. 19. 20:30

Rookiss 님의 '[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버' 를 들으며 배운 내용을 정리하고 있습니다. 관심이 있으신 분은 꼭 한 번 들어보시기를 추천합니다!

[C++ 게임 서버] 1-13. CPU 파이프라인 (feat. 코드 재배치)

핵심 :

1. 컴파일러는 어셈블리어를 배치하는 과정에서 코드 재배치를 할 수 있다. 이 과정에서 의도치 않은 결과가 도출될 수 있다.

2. CPU 파이프라인은 CPU 가 명령어를 효율적으로 하기 위한 시스템이다.

CPU 파이프라인에 대해 공부를 하게 되면, 최적화를 위해 컴파일러가 임의로 어셈블리 코드를 재배치한다는 것을 알 수 있다.

싱글 쓰레드에서는 순차적으로 로직이 실행됨이 보장되지만, 멀티 쓰레드에서는 꼭 의도대로 프로그램이 동작하지 않을 수 있는데... 다음 코드를 보자.

volatile bool ready = false;

// 가시성, 코드 재배치
int32 x = 0;
int32 y = 0;
int32 r1 = 0;
int32 r2 = 0;

void foo()
{
	while (!ready)
		;

	x = 1;  // store x
	r1 = y; // Load y
}

void poo()
{
	while (!ready)
		;

	y = 1;	// store y
	r2 = x;	// Load x
}

int main()
{
	int count = 0;

	while (true)
	{
		ready = false;
		count++;

		x = y = r1 = r2 = 0;

		thread t1(foo);
		thread t2(poo);

		ready = true;

		t1.join();
		t2.join();

		if (r1 == 0 && r2 == 0)
		{
			break;
		}
	}

	cout << count << " 번 만에 빠져나옴!" << endl;
}

싱글 쓰레드에서는 main 함수에서의 while 문이 break 되는 경우가 없겠지만, 멀티 쓰레드 환경에서는 이와 같은 동작이가능하다.

그 이유는, foo 와 poo 에서 store 과 load 의 순서를 바꾸어 컴파일러가 실행될 수도 있기 때문이다. 컴파일러 입장에서 store 과 load 사이에는 로직상의 연관성이 없으므로, 임의로 바꾸어 실행을 할 수 있게 된다 😅 (꼭 그런 것은 아니고, 효율적이라고 판단했을 때 그런 행동을 할 것이다)

 

실제로 CPU 에서의 작업은 instruction 단위로 이루어져 있는데, store, load 등의 순서를 최적화하는 과정에서 우리가 의도하지 않은 결과가 나타날 수 있다는 것만 알아두자! 😊

Comments