CPU는 작업을 어떻게 처리할까?

심규민·2024년 10월 14일
post-thumbnail

아마 운영체제에 대해서 공부하다면 자연스럽게 다음과 같은 궁금증이 생기곤 했습니다.

  • cpu 읽기 속도를 더 빠르게 만들 수 없을까?
  • 특정 작업이 가끔 느려지는 이유가 뭘까?
  • cpu 위주 작업이 많을 때 특정 작업을 먼저 처리할 수 있을까?
  • 멀티 스레드 환경에서 읽기, 쓰기에서의 동기화가 어떻게 동작하는 걸까?

사실 위 질문들에 대해서 CPU가 어떻게 작업을 처리하는지에 대해서 안다면 답할 수 있습니다.

CPU 읽기, 쓰기는 어떻게 이뤄질까?

cpu에 대해서 시작하기에 앞서 cpu의 아키텍처에 대해서 알고 있으면 앞으로의 이해에 도움이 됩니다. 다음 다이어그램을 통해 한번 확인해볼까요?

위 다이어그램에서는 현대 cpu의 아키텍처를 보여주고 있습니다. 우선 물리적 코어는 2개로 각각 2개의 논리적 스레드를 가지고 있습니다. 그리고 각 스레드들은 L1 캐시를 가지고 있습니다. 이 캐시 영역은 dCacheiCache 영역으로 분리되어 있습니다.(이에 대한 자세한 정리는 다음에...) 이러한 1차 캐시는 각 스레드에서만 접근 가능한 영역이며 다른 스레드에서는 접근할 수 없는 고유한 영역입니다.

L1 캐시 아래에 위치한 물리적 코어 내부에서 공유되는 메모리 영역인 L2 캐시가 존재합니다. L2 캐시는 앞서 설명한 것처럼 물리적 코어 내부에서 공유되는 메모리 영역이기에 Core 0, Core 1 모두 접근 가능합니다. 하지만 이 역시 물리적 코어 내부에서만 공유되기 때문에 Processor 1에서는 Processor 0의 2차 캐시 영역에 접근할 수 없습니다.

L2 아래에 위치한 L3 캐시는 메모리와 cpu 코어들 사이에 위치한 공유 메모리 공간이라고 생각할 수 있습니다.

이 구조를 보면서 캐시 영역이 계층적으로 이뤄져 있으며, 아래 계층일수록 많은 코어들이 공유하는 영역임을 알 수 있습니다.

이러한 CPU 아키텍처의 메모리 계층 구조에 대해서는 또 다른 특징으로 아래 계층으로 갈수록 용량은 늘어나지만 속도는 느려진다는 특징을 가집니다! 이 특징이 성능 최적화에서 알아야할 제일 중요한 부분입니다.

캐시 미스(Cache Miss)

우선 캐시 영역에 대해 접근할 때의 시간(L1은 0.5ns, L2는 10ns)과 메모리 영역에 접근할 때의 시간(100ns)을 기반으로 다음과 같은 상황을 생각해볼 수 있습니다.

만약 cpu 내의 캐시에서 데이터가 유효하지 않아(invalid data/cache) 메모리에 접근하여 데이터를 다시 읽어오는 경우 캐시에 비해 최소 10배는 느리기에 성능 저하가 발생합니다.

이 문제를 흔히 The Cache False Sharing 문제라고 부릅니다.

그러면 이 문제에 대해서 좀 더 자세히 알아볼까요?

위 그림에서는 두 cpu에서 동시에 동작하고 있다고 가정해봅시다.

두 스레드는 서로 다른 데이터를 메모리로부터 조회한 뒤 캐시에 저장하고 있습니다. 하지만 두 스레드에서 가져온 데이터는 연속된 메모리 공간에 저장되어 있습니다. 따라서! 캐싱할 때 데이터를 가져오는 단위인 캐시 라인에 두 스레드에서 가져오는 데이터가 함께 포함되며, 각 캐시 공간에는 동일한 캐시 데이터가 저장되게 됩니다.

조금 더 쉽게 설명하면 특정 영역에 위치한 서로 다른 데이터를 조회하였지만, 데이터를 가져올 때 해당 영역의 데이터를 전부 조회하게 되어 두 스레드는 동일한 데이터를 캐싱하고 있게 됩니다.

따라서 두 스레드가 각각의 캐시 영역의 서로 다른 데이터를 수정하더라고, 하나의 스레드가 먼저 데이터를 변경하게 되면 다른 스레드에서의 캐시 데이터는 무효화가 되며, 새로운 데이터로 덮어쓰기를 진행합니다.

The Cache False Sharing 문제는 성능을 매우 저해하는 문제입니다. 따라서 개발을 진행할 때 데이터가 빈번하게 변경된다면 The Cache False Sharing 문제에 대해서 한 번 더 생각해봐야 합니다.

추가로 이는 특정 객체가 여러 캐시 라인에 걸쳐 저장되어 있는 경우에는 더 많은 캐시들이 무효화될 수 있기 때문에 c언어에서는 ____cacheline_aligned을 통해 구조체의 데이터들을 캐시 라인에 맞게 맞춰주는 기능을 제공합니다.

이러한 문제들은 결국 여러 cpu들이 특정 데이터에 대해 접근하고 수정하는 경쟁 상태(race condition)일 때 성능이 저하됨을 유추할 수 있습니다.

그렇다면 CPU는 어떤 스레드 실행시킬지 어떻게 결정할까요?

실행 큐(Run Queue)

모두가 아시다싶이 시스템은 cpu 코어 수보다 많은 수의 스레드들의 동작으로 이뤄집니다. 즉, 반대로 생각해보면 각 cpu 코어들은 각자의 실행 큐(runqueue)를 가지며 스레드들을 처리하게 될겁니다. 이를 간단히 그림으로 표현하면 다음과 같습니다.

cpu 각 코어들은 작업 처리를 위한 큐(runqueue)를 자기게 되며, 작업을 가지는 스레드들이 이 큐에 저장되게 됩니다. 추가로 리눅스 커널에서는 스레드들간 작업 우선순위를 부여하여 우선순위가 높은 작업이 먼저 처리될 수 있도록 도와주는 여러 스케쥴링을 제공합니다.

위 스케쥴링들은 Deadline > Realtime > Fair와 같은 우선순위를 가집니다.

CPU에서 다음 작업 처리를 위해 대기중인 스레드들을 확인할 때 앞선 우선순위를 기반으로 다음 작업을 선택합니다.

이러한 우선순위에 따라 현재 작업의 상태를 다양하게 설정할 수 있습니다. 예를들면 낮은 지연이 중요한 임베디드 시스템의 경우 작업 상태를 Realtiem으로 설정하게되어 일반적인 작업보다 우선되게 처리할 수 있습니다.

지금까지 CPU의 간단한 메모리 구조와 메모리 구조에서 발생할 수 있는 문제, 그리고 CPU의 스케쥴링 종류에 대해서 간단히 정리해보았습니다. 그렇다면 처음에 던진 질문에 대해서 다시 답해보면 다음과 같이 답할 수 있습니다.

  • cpu 읽기 속도를 더 빠르게 만들 수 없을까? <- 계층적 메모리 구조를 통해 데이터 접근 속도를 높이면 될 것 같아요
  • 특정 작업이 가끔 느려지는 이유가 뭘까? <- 예상하기로는 캐시 무효화로 인한 재캐싱 또는 스레드 우선순위에서 밀려 처리 시간이 느려지는 것 같아요.
  • cpu 위주 작업이 많을 때 특정 작업을 먼저 처리할 수 있을까? <- 우선순위를 높이면 될 것 같아요!
  • 멀티 스레드 환경에서 읽기, 쓰기에서의 동기화가 어떻게 동작하는 걸까? <- 캐시 무효화를 통해 데이터 동기화를 진행합니다!

이렇게 cpu 구조에 대해서 조금 공부하다보니 컨텍스트 스위칭이 궁금해졌습니다. 다음에는 컨텍스트 스위칭(Context Switching)에 대해서 정리해보겠습니다!

요약

  • CPU의 계층적 메모리 구조는 성능에 매우 중요한 영향을 가집니다.
  • CPU 계층적 메모리 구조로 인해 메모리 무효화(The Cache False Sharing) 문제가 발생할 수 있습니다.
  • 다양한 스레드가 처리되는 환경에서 각 CPU 코어들은 실행 큐를 가지며, 큐 내부에서 다양한 스케쥴링이 존재합니다.

참고
https://medium.com/@cstoppgmr/how-does-the-cpu-execute-tasks-f63a8517bbe7
https://medium.com/@ali.gelenler/cache-trashing-and-false-sharing-ce044d131fc0
https://kldp.org/node/23286

0개의 댓글