taskset/cpuset으로 코어 바인딩하기

CPU 시간을 얼마나 쓸지(cpu.max/cpu.weight)와 어느 코어에서 실행될지는 별개 문제다. NUMA 지역성이 중요하거나 특정 코어를 인터럽트 처리용으로 비워둬야 한다면 사용량 제한이 아니라 실행 코어 자체를 고정해야 한다. 이 글에서는 프로세스 하나를 고정하는 taskset과, 여러 프로세스를 묶어 코어 집합을 강제하는 cpuset cgroup을 실제로 비교한다.

taskset — 단일 프로세스 고정

/proc/<pid>/stat의 39번째 필드(processor)가 실행 중인 코어 번호다. 무한 루프 프로세스를 고정 전후로 샘플링했다.

# 고정 전 (유휴 시스템이라 스케줄러가 CPU4에 계속 머묾)
4 4 4 4 4 4 4 4

$ taskset -pc 3 $PID
pid 19831's current affinity list: 0-5
pid 19831's new affinity list: 3

# 고정 후
3 3 3 3 3 3 3 3

유휴 상태에선 차이가 크지 않지만, 여러 프로세스가 경쟁하는 상황에서는 로드밸런서가 수시로 프로세스를 옮긴다. taskset은 이 이동을 원천 차단해 캐시 지역성을 지킨다.

-p만 쓰면 조회만 하고, 새 프로세스를 처음부터 특정 코어에서 띄우려면 실행 명령 앞에 taskset -c를 붙인다.

# 조회만 (변경 없음)
$ taskset -p $$
pid 27162's current affinity mask: 3f    ← 0x3f = 코어 0~5 전부 허용

# 새 프로세스를 코어 2,4로 한정해서 시작
$ taskset -c 2,4 python3 -c "
import os
with open(f'/proc/{os.getpid()}/status') as f:
    for line in f:
        if 'Cpus_allowed_list' in line:
            print(line.strip())
"
Cpus_allowed_list:	2,4

affinity는 nproc처럼 CPU 개수를 세는 명령에도 그대로 반영된다.

$ nproc
6
$ taskset -c 4,5 nproc
2

make -j$(nproc)처럼 코어 수를 자동 감지하는 빌드 스크립트를 taskset -c로 감싸면, 별도 옵션 없이도 실제 허용된 코어 수만큼만 병렬 작업을 띄운다.

cpuset cgroup — 여러 프로세스를 코어 집합에 묶기

프로세스 여러 개를 한 코어 집합에 묶으려면 taskset 반복 대신 cpuset.cpus로 cgroup 자체에 코어 집합을 지정한다.

$ sudo mkdir /sys/fs/cgroup/cpusettest
$ echo "4-5" | sudo tee /sys/fs/cgroup/cpusettest/cpuset.cpus

# 무한 루프 프로세스 3개를 이 cgroup에 추가
$ echo $PID | sudo tee /sys/fs/cgroup/cpusettest/cgroup.procs
$ for p in $PIDS; do awk '{print "pid=" $1 " psr=" $39}' /proc/$p/stat; done
pid=20111 psr=4
pid=20126 psr=5
pid=20139 psr=4

# 1초 뒤 재확인 — 여전히 4 또는 5만 나온다
pid=20111 psr=4
pid=20126 psr=5
pid=20139 psr=4

세 프로세스 모두 4, 5번 코어에서만 관측됐다. 새로 추가되는 자식 프로세스도 별도 설정 없이 같은 제약을 자동으로 물려받는다는 게 taskset과의 가장 큰 차이다.

cpuset이 taskset보다 우선한다

위 cgroup(코어 4-5로 제한) 안의 프로세스에 taskset으로 범위 밖 코어를 지정하면 어떻게 될까.

$ taskset -pc 0-2 $PID
taskset: failed to set pid 29429's affinity: Invalid argument
$ taskset -p $PID
pid 29429's current affinity list: 4,5   ← 변경되지 않고 그대로

커널이 요청 자체를 거부한다. taskset은 항상 그 프로세스가 속한 cpuset 범위 안에서만 재조정할 수 있고, cgroup의 제약을 넘어설 수 없다.

둘 중 무엇을 쓸지

상황선택
이미 떠 있는 프로세스 하나를 즉석에서 고정taskset -pc
서비스와 워커/자식 프로세스 전부를 특정 코어에cpuset cgroup
IRQ 처리용 코어를 비우고 나머지에만 배치cpuset.cpus + IRQBALANCE_BANNED_CPULIST로 인터럽트도 배제
systemd 서비스유닛 파일 CPUAffinity=로 수동 cgroup 조작 없이 적용

주의사항

항목내용
과도한 고정은 역효과코어를 너무 좁게 묶으면 그 코어만 과부하되고 나머지는 논다. 지역성이 정말 중요한 프로세스만 선별적으로
taskset은 재시작하면 초기화영구 적용은 systemd CPUAffinity=나 시작 스크립트에 포함
cpuset.cpus는 상위 cgroup 범위 안에서만부모의 cpuset.cpus.effective에 없는 코어는 자식에서 지정해도 무시된다
NUMA 환경에서는 cpuset.mems도 함께코어만 묶고 메모리 노드를 지정하지 않으면 원격 노드 접근으로 지연이 늘 수 있다

마무리

단발성 고정은 taskset으로 충분하지만, 서비스와 자식 프로세스 여러 개를 일관되게 묶어야 한다면 cpuset cgroup이 관리 부담이 적고 taskset보다 우선 적용된다. IRQ affinity와 함께 쓰면 특정 코어를 애플리케이션 전용으로 완전히 격리할 수 있다.

참고

Kernel.org – cgroup v2 cpuset Interface Files

답글 남기기