운영체제 - 컴퓨터 구조 원리 : CPU 파이프라인

rizz·2024년 2월 28일
0

운영체제

목록 보기
3/3

📒 컴퓨터 구조 원리 : CPU 파이프라인

📌 CPU Pipeline

  • Stage 1 : 명령어 가져옴(Fetch) -> Stage 2 : 명령어 해석(Decode) -> Stage 3 : 명령어 실행(Execute) -> Stage 4 : 결과 반환(Write-back)
  • CPU에서 코어마다 별도의 캐시를 갖고 있다.
  • 우리는 컴파일러가 우리의 코드를 기계어로 변환하고 그 기계어를 CPU가 실행한다고 알고 있다.
  • 그러나 컴파일러가 우리의 코드를 읽는 그대로 번역하지 않을 수도 있다.
  • 예를 들어, 컴파일러가 코드를 읽다가 코드의 순서를 바꾸면 더 빠르게 동작할 것 같다 싶으면 실제로 코드의 순서를 바꿔 번역하게 되는 것이다.
  • 가시성 : ex) 멀티 쓰레드 상황에서 1을 대입했는데 다른 쓰레드에서는 1을 읽지 못할 수 있다.
  • 코드 재배치 : 컴파일러가 최적화를 위해 코드의 순서를 바꿨을 때 코드의 논리적인 문제가 없다면 코드의 순서를 바꿀 수도 있다.
  • 컴파일러는 싱클 쓰레드 환경을 기준으로 분석하기 때문이다. (CPU 또한 이런 행동을 할 수 있다.)
  • 싱글 쓰레드 환경에서는 문제가 없을 수 있지만, 멀티 쓰레드 환경에서는 여러 가지 문제를 발생시킨다.

예제

#include "pch.h"
#include "CorePch.h"
#include <atomic>
#include <iostream>
#include <mutex>
#include <thread>
#include <windows.h>

int32 x = 0;
int32 y = 0;
int32 r1 = 0;
int32 r2 = 0;

volatile bool ready;

void Thread_1()
{
	while (!ready);
	y = 1; // store y
	r1 = x; // load x
}

void Thread_2()
{
	while (!ready);
	x = 1; // store x
	r2 = y; // load y
}

int main()
{
	int32 count = 0;

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

		x = y = r1 = r2 = 0;

		thread t1(Thread_1);
		thread t2(Thread_2);

		ready = true;

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

		if (r1 == 0 && r2 == 0)
		{
			break;
		}
	}
}
  • 컴파일러는 멀티 쓰레드는 생각하지 않고 단일 쓰레드 기준으로 코드를 분석하는 데, load를 먼저 하고 store를 하는 게 성능적으로 이득이고, 실질적으로 함수의 로직이 변하지 않는다고 생각되면 순서를 바꿀 수도 있다.
    • 그래서 위 예제의 while 문이 끝나는 상황이 나오지 않을 것 같지만 결국에는 끝나게 된다.
  • 물론 항상 이렇게 동작하는 것은 아니고 단일 쓰레드에서 로직 및 결과물이 같다는 보장이 있을 때 이런 식으로 동작한다.
  • 심지어 컴파일러가 위와 같이 동작하지 않는다 해도 CPU가 멋대로 위와 같은 행동을 할 수 있다.
  • 싱글 쓰레드에서는 순서를 위와 같은 상황에서 순서를 바꾸어도 문제가 없기 때문에 우리가 몰랐던 것이다.
  • 그러나 멀티 쓰레드 환경에서 위와 같은 상황은 치명적일 수 있다.
  • C++11 이전에는 모든 모델이 싱글 쓰레드 기준으로 생각했지만, C++11부터는 C++ 표준도 멀티 쓰레드에 대해 고려해 주기 시작하였다.
  • C++에서 제공하는 방법들을 활용하여 코드를 작성하게 되면 어떠한 모델이건 프로세스이건 상관없이 표준에 의해 제어가 되기 때문에 위와 같은 상황을 방지할 수 있다.
profile
복습하기 위해 쓰는 글

0개의 댓글