개요
통신 앱은 대부분 Multi-Processing 및 Multi-Threading이 사용되어서 개발된다.
그러다보니 시스템에 존재하는 CPU Core 수에 따라, 앱 성능이 천차만별로 달라지는 것은 당연지사이다.
여러 Task를 한번에 처리가능한 CPU Core가 많아지면 그만큼 성능이 높아지는 것은 당연한 것이고, Core가 적으면 많은 Task를 한번에 처리할 수 없게되어 그만큼 성능이 낮아지는 것은 당연하기 때문이다.
taskset은 특정 앱에서 몇 개의 Core를 사용하는 것이 가장 적절한지를 실험해볼 수 있도록 지원해주는 리눅스 유틸리티이다.
본 페이지에서는 이러한 taskset 유틸리티에 대해서 알아볼 것이다. 먼저, taskset을 설치하는 방법을 설명하고 그 후 taskset 사용법에 대해서 설명한다. 마지막으로는 taskset에 대한 예제로 10개의 Thread를 한번에 실행시키는 앱을 테스트하여 성능이 어떻게 달라지는지 확인해 볼 것이다.
taskset 설치
taskset 유틸리티는 아래 명령을 통해 설치가 가능하다.
util-linux 패키지는 기본적으로 설치된 경우가 많을 것이다.
$ sudo apt-get install util-linux
# taskset이 시스템 상에 설치되어 있는지 확인하기 위해 버전 출력
$ taskset --version
taskset 명령
$ taskset [Options] [core_idx] <pid | bin_name>
# 특정 실행 파일을 [core_idx]에 위치한 CPU Core로 실행시킨다.
$ taskset [core_idx] <bin_name>
# -p 옵션은 <pid>와 함께 쓰인다.
# 해당 옵션은 특정 pid를 소유한 Process에게 할당된 CPU Core를 출력시켜준다.
$ taskset -p <pid>
# -c 옵션은 -p 옵션과 함께 쓰인다.
# -p 옵션만 사용하게 되면, 출력 값이 CPU Mask(16진수) 형태로 출력되어 보기가 어렵다.
# 이때, -c 옵션을 같이 사용하게 되면, 출력 값이 10진수로 형태의 CPU Index 형태로 보여준다.
# 즉, -pc 옵션을 통해 특정 PID를 소유한 Proces에게 할당된 CPU Core를 보기 쉽게 출력해준다.
$ taskset -pc <pid>
# Example) main 실행 파일을 7번째에 위치한 CPU Core에 할당시켜 실행
$ taskset -c 7 ./main
# Example) main 실행 파일을 3-8번째에 위치한 CPU Core에 할당시켜 실행
$ taskset -c 3-8 ./main
# Example) 현재 동작 중인 Process(pid = 65123)에 10번째 Index에 위치한 CPU Core를 할당
$ taskset -pc 10 65123
# Example) 현재 동작 중인 Process(pid = 65123)에 0,7번째 Index에 위치한 CPU Core를 할당
$ taskset -pc 0,7 65123
# Example) 현재 동작 중인 Process(pid = 65123)에 2-7번째 Index에 위치한 CPU Core를 할당
$ taskset -pc 2-7 65123
taskset 예제
taskset 유틸리티를 통해 Process에 CPU Core를 할당시킬 수 있는지 실험하기 위해, 아래와 같은 while.c 파일을 생성해주었다.
// @File : while.c
#include <stdio.h>
#include <time.h>
#include <pthread.h>
void *thread_func(void *data)
{
unsigned long long i;
for(i=0; i<10000000000; i++);
}
void main()
{
pthread_t pthread0, pthread1, pthread2, pthread3, pthread4;
pthread_t pthread5, pthread6, pthread7, pthread8, pthread9;
while(1) {
struct timespec begin, end;
char s = '\0';
printf("start >> ");
fflush(stdin);
s = getchar();
getchar();
if(s >= 'a' && s <= 'z'){
clock_gettime(CLOCK_MONOTONIC, &begin);
pthread_create(&pthread0, NULL, thread_func, NULL);
pthread_create(&pthread1, NULL, thread_func, NULL);
pthread_create(&pthread2, NULL, thread_func, NULL);
pthread_create(&pthread3, NULL, thread_func, NULL);
pthread_create(&pthread4, NULL, thread_func, NULL);
pthread_create(&pthread5, NULL, thread_func, NULL);
pthread_create(&pthread6, NULL, thread_func, NULL);
pthread_create(&pthread7, NULL, thread_func, NULL);
pthread_create(&pthread8, NULL, thread_func, NULL);
pthread_create(&pthread9, NULL, thread_func, NULL);
pthread_join(pthread0, NULL);
pthread_join(pthread1, NULL);
pthread_join(pthread2, NULL);
pthread_join(pthread3, NULL);
pthread_join(pthread4, NULL);
pthread_join(pthread5, NULL);
pthread_join(pthread6, NULL);
pthread_join(pthread7, NULL);
pthread_join(pthread8, NULL);
pthread_join(pthread9, NULL);
clock_gettime(CLOCK_MONOTONIC, &end);
printf("%.2lfs\n", (end.tv_sec-begin.tv_sec) + (end.tv_nsec-begin.tv_nsec) / 1000000000.0);
}
else{
continue;
}
}
}
해당 프로그램은 Main Thread에서 10개의 Sub Thread를 실행시켜, 각 Sub Thread에서 10,000,000,000번의 반복을 수행하는 for문이 실행되도록 하였다. 10개의 Sub Thread를 실행시킨 Main Thread는 Sub Thread가 모두 종료될 때 까지 대기 하고 모든 Sub Thread의 연산이 끝날 때까지 걸린 시간을 출력하게 된다.
해당 코드(while.c)의 컴파일은 아래 명령을 따르면 된다.
$ gcc -o while while.c -lpthread
while.c를 컴파일하여 while 바이너리(실행파일)를 생성해주었다면, 이제 taskset으로 while Process에 여러 다른 개수의 CPU Core를 할당시켜 결과가 어떻게 달라지는지 확인해본다.
이러한 실험 과정은 3개의 터미널 창을 통해 진행하였다. 실험 과정은 1. 부터 순서대로 설명한다.
1. 먼저, 터미널 1에서 while 바이너리를 실행
2. 터미널 2에서 실행된 while 바이너리의 Process ID 확인
3. 터미널 2에서 while에 할당된 CPU Core 개수를 확인(앞서 2.에서 확인한 Process ID를 이용)
실험 PC는 총 12개의 Core를 갖고있음으로 0-11이 할당되어 있는 것을 확인할 수 있었다.
4. 터미널 3에서 top 명령을 통해 각 CPU Core 별 점유율을 0.5초 간격으로 조회
top 명령을 실행한 뒤 '1'을 눌러야 CPU Core 별 점유율을 확인할 수 있다.
5. 터미널 1에서 while 바이너리가 실행되었다면, start >> 프롬프트가 뜰 것이다. 해당 프롬프트에서 알파벳 a~z를 입력해주면 10개의 Sub Thread가 실행된 뒤 연산을 수행하고 모든 Sub Thread가 종료된 시간을 출력해준다.
6. 터미널 3에서는 10개의 Sub Thread를 실행시키기 위해 총 10개의 CPU Core가 사용된 것을 확인할 수 있을 것이다.
7. 터미널 1에서는 10개의 Core를 점유한 Process의 실행결과를 확인가능
10개의 Core를 점유했을 경우, 대략 6.5초 소요되는 것을 확인할 수 있다.
8. 터미널 2에서 taskset 명령을 통해 실행 중인 while Process에 0번째 Index에 위치한 CPU Core를 할당
9. 터미널 1에서 알파벳을 입력하여 10개의 Sub Thread를 실행
10. 터미널 3에서 0번째에 위치한 CPU Core가 점유되어 실행되는 것을 확인가능
11. 터미널 1에서 하나의 Core만 사용한 경우에는 대략 26초가 걸리는 것을 확인가능
12. 터미널 2에서 다시 taskset 명령을 통해 Process에 2, 3, 7 번째 Index에 위치한 CPU Core를 할당
13. 터미널 1에서 알파벳을 입력하여 10개의 Sub Thread를 실행
14. 터미널 3에서 2, 3, 7번째에 위치한 CPU Core가 점유되어 실행되는 것을 확인가능
15. 터미널 1에서는 점유된 CPU Core 개수에 따라, 실행결과가 다르게 출력되는 것을 확인가능
References
https://groups.google.com/g/sysadminstudy/c/t9dBvajsPSk
'Linux' 카테고리의 다른 글
리눅스 clang, llvm 버전 관리 스크립트 (update-alternatives) (0) | 2023.11.13 |
---|---|
리눅스 Ubuntu에서 노트북 닫아도 대기모드로 빠지지 않게 하기 (0) | 2023.11.12 |
리눅스 시스템 다운 시 발생되는 커널 함수, kernel_power_off() (0) | 2023.07.28 |
리눅스 Network Device Driver의 NAPI 모드 (0) | 2023.07.03 |
리눅스 커널 타이머, Linux Kernel Timer (Kernel 4.14.x 이상) (0) | 2023.06.16 |