System Programming

[C/CPU-affinity] 프로세스 CPU Affinity 설정

uzguns 2024. 11. 13. 15:26

CPU Affinity 란

Asymmetric multiprocessing (AMP)와 symmetric multiprocessing (SMP)는 여러 프로세서를 사용하는 멀티프로세서 시스템에서의 처리 방식이다. 이 두 방식의 차이점을 이해하면 멀티프로세서 환경에서 프로세서가 어떻게 상호작용하고 작업을 분배하는지 알 수 있다.

  • Asymmetric Multiprocessing (AMP)
    • 하나의 메인 프로세서만 시스템 리소스(예: 메모리, 입출력 장치 등)에 직접 접근하고, 시스템의 데이터 구조에 액세스한다.
    • 장점은 시스템 설계가 단순하고, 동기화에 따른 오버헤드가 줄어들어 성능이 향상된다
    • 단점은 하나의 프로세서에 작업이 집중될 수 있다는 점이며, 시스템 확장성(scalability)에서 한계가 있다.
  • Symmetric Multiprocessing (SMP)
    • 모든 프로세서가 동등한 역할을 하며, 각 프로세서가 시스템 리소스에 직접 접근할 수 있다.
    • 각 프로세서는 자가 스케줄링이 가능하며, 공통 데이터 구조인 준비 큐(ready queue) 또는 프로세서 전용의 준비 큐를 통해 작업을 할당받고, 필요할 때마다 이를 갱신한다.
    • 이 방식은 모든 프로세서가 동시에 여러 작업을 처리할 수 있으므로 처리 속도가 향상된다.
    • 장점은 확장성이 뛰어나고 시스템 내 모든 프로세서를 효율적으로 사용할 수 있다
    • 단점은 동기화 비용이 증가할 수 있으며, 작업이 많을 때 데이터 경합이 발생할 가능성이 높다

 

이 멀티 코어 환경에서는 프로세스가 특정 CPU 코어에서 실행될 수 있도록 CPU Affinity 를 설정할 수 있다. 

Affinity를 쉽게 얘기하면 특정 프로세스를 특정 cpu에 assign 하여 퍼포먼스를 최적화하는 방법이다. 

 

운영체제는 각 코어 별로 프로세스를 스케쥴링 하는 schedule list가 있다. core 별로 있기 때문에 내가 동작 시킨 프로그램이 core 2에서 돌아갈 수도 있고 core 3에서 돌아갈 수도 있다. 이걸 그 때 그 때 cpu가 판단하는 건데 기본적으로 스케쥴러는 성능적인 요인이 없는 경우 가능한 오랫동안 동일한 cpu에서 동작하도록 프로세스를 유지하려고 한다. 

 

만약 내가 돌리는 프로그램이 무거운 작업을 하는 thread 3개를 돌린다면 main process는 core 1에, 각 thread 는 core 2, 3, 4에 affinity 로 지정한다면 프로그램이 cpu 를 최대한 활용하여 효율적으로 동작할 수 있다.

 

Affinity System Call

sched_setaffinity, sched_getaffinity

#define _GNU_SOURCE
#include <sched.h>

int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);

 

  • sched_setaffinity: 특정 프로세스의 CPU affinity를 설정하는 함수.
  • sched_getaffinity: 특정 프로세스의 CPU affinity를 조회하는 함수.
  • pid: 프로세스 ID를 나타내며, 0을 지정하면 호출한 프로세스의 ID가 사용됩니다.
  • cpusetsize: mask가 가리키는 데이터의 크기(바이트 단위)이며, 보통 sizeof(cpu_set_t)로 지정됩니다.
  • mask: CPU affinity를 설정하거나 가져올 때 사용하는 cpu_set_t 구조체의 포인터.

사용 예시

pid_t pid = getpid(); // 현재 프로세스의 ID
cpu_set_t mask;
CPU_ZERO(&mask);       // CPU 집합 초기화
CPU_SET(0, &mask);     // 0번 CPU에 할당

// 현재 프로세스를 0번 CPU에만 할당
if (sched_setaffinity(pid, sizeof(mask), &mask) == -1) {
    perror("sched_setaffinity");
    return EXIT_FAILURE;
}

// 현재 프로세스가 할당된 CPU 확인
if (sched_getaffinity(pid, sizeof(mask), &mask) == -1) {
    perror("sched_getaffinity");
    return EXIT_FAILURE;
}

if (CPU_ISSET(0, &mask)) {
    printf("Process is running on CPU 0\n");
}

 

 

CPU_SET, CPU_ISET

CPU_SET은 core 번호를 mask에 넣는 것이고 CPU_ISSET은 core 번호와 mask 값이 일치하면 true를 리턴하는 매크로 이다.

 

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    cpu_set_t set;         // CPU 집합 선언
    int cpu;               // CPU 번호를 저장할 변수

    // CPU 집합을 초기화하여 비어있는 상태로 만듭니다.
    CPU_ZERO(&set);

    // CPU 0, 2, 4를 집합에 추가합니다.
    CPU_SET(0, &set);
    CPU_SET(2, &set);
    CPU_SET(4, &set);

    // 설정된 CPU 집합을 출력합니다.
    printf("CPU set includes:\n");
    for (cpu = 0; cpu < CPU_SETSIZE; cpu++) {
        if (CPU_ISSET(cpu, &set)) {
            printf("CPU %d is set\n", cpu);
        }
    }

    // 특정 CPU가 집합에 포함되어 있는지 확인합니다.
    int check_cpu = 2;
    if (CPU_ISSET(check_cpu, &set)) {
        printf("CPU %d is included in the set\n", check_cpu);
    } else {
        printf("CPU %d is not included in the set\n", check_cpu);
    }

    check_cpu = 3;
    if (CPU_ISSET(check_cpu, &set)) {
        printf("CPU %d is included in the set\n", check_cpu);
    } else {
        printf("CPU %d is not included in the set\n", check_cpu);
    }

    return 0;
}