CLOCK_PROCESS_CPUTIME_ID
고친 사람 github-actions[bot]
CLOCK_PROCESS_CPUTIME_ID 는 프로그램이 계산에 쓴 시간을 잴 때 운영체제에서 골라 읽는 시계입니다. 이 시계는 프로그램이 계산하는 동안에만 갑니다. 데이터베이스의 응답을 기다리는 동안에는 멈춰 있습니다. 그래서 걸린 시간이 아니라 일한 시간을 알려 줍니다.
쉽고 빠른 이해
무슨 일을 하는 물건인가 — 운영체제가 들고 있는 여러 시계 가운데 「이 프로그램이 계산에 쓴 시간」만 세는 시계를 고르는 이름입니다. 0.22초 걸린 요청이 계산에는 0.02초만 썼다는 것을 이 시계로 압니다.
왜 이렇게 하나 — 걸린 시간만 보면 느린 까닭을 모릅니다. 계산이 무거워서 느린지, 다른 곳의 응답을 기다려서 느린지가 갈리지 않습니다. 계산에 쓴 시간을 따로 재면 둘이 갈립니다.
어떻게 도나
- 일을 시작하기 전에 이 시계를 읽습니다
- 일이 끝나면 한 번 더 읽습니다
- 뒤 값에서 앞 값을 빼면 그 사이 계산에 쓴 시간입니다
대가 — 기다린 시간은 안 잡힙니다. 사용자가 느끼는 응답 시간은 이 시계로 잴 수 없습니다. 스레드 여럿이 함께 일하면 값이 걸린 시간보다 커지기도 합니다.
상세
가게에 수리 기사를 불렀다고 해 보세요. 기사는 아침부터 저녁까지 가게에 있었습니다. 그런데 청구서에는 공구를 들고 손을 움직인 네 시간만 적힙니다. 부품이 오기를 기다리며 앉아 있던 시간은 빠집니다. 기사 셋이 한 시간씩 같이 일하면 청구서에는 세 시간이 적힙니다.
CLOCK_PROCESS_CPUTIME_ID 는 운영체제가 프로그램마다 적어 두는 이 청구서입니다.
운영체제의 시계 가운데 하나를 고르는 이름
응용 프로그램은 시간을 스스로 세지 않습니다. 운영체제의 중심부인 커널에 물어봅니다. 커널은 쓰임이 다른 시계를 여러 개 들고 있습니다. 어느 시계를 읽을지는 이름을 넘겨 고릅니다.
그 이름들은 POSIX(Portable Operating System Interface, 이식 가능한 운영체제 인터페이스)가 정해 둡니다. POSIX 는 리눅스와 macOS 같은 유닉스 계열 운영체제가 함께 지키는 규격입니다. C 프로그램에서는 이 이름을 헤더 time.h 에 정의된 상수로 씁니다.
프로세스는 실행 중인 프로그램 하나입니다. 서버 프로그램 하나를 띄우면 프로세스가 하나 생깁니다.
이 이름은 네 조각으로 읽습니다. CLOCK 은 시계, PROCESS 는 프로세스, CPUTIME 은 CPU(Central Processing Unit, 중앙 처리 장치) 시간, ID(identifier, 식별자)는 고르는 이름이라는 뜻입니다. 합치면 「이 프로세스의 CPU 시간을 세는 시계」입니다. 이 이름을 넘겨 시계를 읽은 바로 그 프로세스가 잴 대상입니다.
이 시계는 모든 운영체제에 있지는 않습니다. 리눅스와 macOS 에는 있습니다.
CPU 시간
CPU 는 프로그램의 명령을 하나씩 실행하는 부품입니다. 요즘 CPU 에는 명령을 따로따로 실행하는 코어가 여럿 들어 있습니다. 코어 하나는 한순간에 한 프로그램의 명령만 실행합니다.
그래서 커널은 코어를 여러 프로세스에 번갈아 나눠 줍니다. 한 프로세스가 잠깐 돌고 나면 다음 프로세스가 돕니다. 이렇게 순서를 정하는 일을 스케줄링이라고 합니다.
CPU 시간은 코어가 이 프로세스의 명령을 실행한 시간을 모두 더한 값입니다. 커널은 프로세스마다 코어를 쓴 시간을 적어 둡니다. CLOCK_PROCESS_CPUTIME_ID 를 읽으면 이 합계가 돌아옵니다.
이 합계에는 두 가지가 들어갑니다. 첫째는 사용자 시간입니다. 프로세스가 자기 코드를 실행한 시간입니다.
둘째는 시스템 시간입니다. 파일 읽기처럼 프로세스가 부탁한 일을 커널이 대신 한 시간입니다. 부탁한 쪽이 이 프로세스이니 이 시간도 이 프로세스 몫으로 적힙니다.
파일 읽기 하나에는 두 몫이 섞여 있습니다. 커널이 코어로 요청을 처리하고 데이터를 옮기는 몫은 시스템 시간으로 세집니다. 디스크가 데이터를 내주기를 기다리는 몫은 빠집니다.
기다리는 동안은 멈춘다
프로세스가 기다리는 동안에는 코어가 그 프로세스의 명령을 실행하지 않습니다. 그래서 이 시계도 가지 않습니다. 데이터베이스의 응답을 기다릴 때, 디스크에서 파일이 올라오기를 기다릴 때, 일부러 쉬게 하는 sleep 을 불렀을 때가 모두 그렇습니다.
락은 여러 실행 흐름이 한 데이터를 동시에 고치지 못하게 막는 장치입니다. 락이 풀리기를 잠들어 기다리면 이 시계도 멈춥니다. 잠든 쪽은 락이 풀릴 때까지 아무 명령도 실행하지 않습니다. 잠들지 않고 락을 계속 확인하며 기다리면 그동안은 시계가 갑니다.
다른 프로세스에 코어를 뺏긴 동안에도 멈춥니다. 이 프로세스는 돌 준비가 되어 있어도 차례가 올 때까지 기다립니다. 그 사이 코어는 다른 프로세스의 명령을 실행합니다.
아래 그림은 요청 하나를 처리하는 동안 두 시계가 무엇을 세는지 보입니다. 비교 대상인 CLOCK_MONOTONIC 은 걸린 시간을 재는 시계입니다. 값이 앞으로만 갑니다. 두 번 읽어 빼면 그 사이 흐른 시간이 됩니다.
flowchart TD
A["요청 해석 · 0.01초 · 두 시계 모두 센다"] --> B["데이터베이스 응답 기다림 · 0.20초 · CLOCK_MONOTONIC 만 센다"]
B --> C["응답 만들기 · 0.01초 · 두 시계 모두 센다"]
C --> D["합계 · CLOCK_MONOTONIC 0.22초 · CLOCK_PROCESS_CPUTIME_ID 0.02초"]
두 값의 차이 0.20초가 곧 데이터베이스를 기다린 시간입니다.
스레드가 여럿이면 걸린 시간보다 빨리 간다
스레드는 프로세스 안에서 따로 도는 실행 흐름입니다. 한 프로세스가 스레드를 여럿 두면 코어 여럿에서 동시에 돌 수 있습니다.
CLOCK_PROCESS_CPUTIME_ID 는 프로세스 안의 스레드가 쓴 CPU 시간을 모두 더합니다. 스레드 넷이 코어 넷에서 1초 동안 쉬지 않고 계산했다고 해 보세요. 걸린 시간은 1초입니다. 이 시계는 4초 늘어납니다.
그래서 이 시계의 값이 걸린 시간보다 큰 것은 이상한 일이 아닙니다. 여러 코어가 이 프로세스를 동시에 돌렸다는 뜻입니다.
걸린 시간과 나란히 읽기
요청 하나 앞뒤에서 CLOCK_MONOTONIC 과 이 시계를 함께 읽으면 걸린 시간이 계산과 기다림으로 갈립니다. 먼저 이 시계를 읽는 호출을 보고, 이어서 두 시계를 함께 읽는 코드를 봅니다.
이 시계는 clock_gettime 이라는 호출로 읽습니다. 첫 인자로 시계 이름을 넘기면 초와 나노초가 돌아옵니다. 나노초는 10억분의 1초입니다.
clock_gettime 은 시스템 콜입니다. 시스템 콜은 프로그램이 커널에 일을 부탁하는 호출입니다. CPU 시간의 합계는 커널이 적어 두니 커널에 물어야 읽을 수 있습니다.
파이썬의 time.clock_gettime 은 C 의 clock_gettime 을 바로 부릅니다. 아래 코드는 요청 하나를 처리하는 함수 handle_request() 앞뒤로 두 시계를 함께 읽습니다. 앞의 그림과 같은 요청입니다.
import time
P = time.CLOCK_PROCESS_CPUTIME_ID
M = time.CLOCK_MONOTONIC
w0 = time.clock_gettime(M)
c0 = time.clock_gettime(P)
handle_request()
w1 = time.clock_gettime(M)
c1 = time.clock_gettime(P)
round(w1 - w0, 2) # 0.22
round(c1 - c0, 2) # 0.02
w1 - w0 은 걸린 시간입니다. c1 - c0 은 CPU 를 쓴 시간입니다. 0.22초 가운데 0.02초만 계산에 썼습니다. 나머지 0.20초는 기다린 시간입니다.
두 값을 견주면 느린 까닭의 방향이 보입니다. 아래 표는 대표적인 세 경우입니다.
| 두 값의 관계 | 대개 이런 뜻 | 손볼 곳 |
|---|---|---|
| CPU 시간이 걸린 시간보다 훨씬 작다 | 대부분 기다렸다 | 데이터베이스 · 네트워크 · 락 |
| 둘이 거의 같다 | 스레드 하나가 쉬지 않고 계산했다 | 계산하는 코드 |
| CPU 시간이 걸린 시간보다 크다 | 스레드 여럿이 동시에 계산했다 | 계산하는 코드 · 스레드 수 |
값의 시작점과 고칠 수 없음
읽은 값은 프로세스가 시작한 뒤 쓴 CPU 시간의 합입니다. 그래서 값 하나만 보아도 뜻이 있습니다. 「이 서버 프로세스는 떠 있는 동안 CPU 를 3분 썼다」가 그런 값입니다.
리눅스에서는 이 시계의 값을 바꿀 수 없습니다. 시계 값을 바꾸는 호출 clock_settime 에 이 이름을 넘기면 호출이 실패합니다. 그래서 읽은 값은 커널이 적은 합계 그대로입니다.
스레드 하나나 다른 프로세스를 재는 형제 시계
CLOCK_PROCESS_CPUTIME_ID 는 이름을 넘긴 프로세스 자신을 잽니다. 스레드 하나만 재거나 다른 프로세스를 재려면 다른 이름을 씁니다.
| 무엇을 재나 | 쓰는 이름 |
|---|---|
| 이 프로세스의 스레드 전부 | CLOCK_PROCESS_CPUTIME_ID |
| 이 이름을 넘긴 스레드 하나 | CLOCK_THREAD_CPUTIME_ID |
| 다른 프로세스 | clock_getcpuclockid 에 프로세스 번호를 넘겨 받은 이름 |
| 같은 프로세스의 다른 스레드 | pthread_getcpuclockid 에 스레드를 넘겨 받은 이름 |
표의 clock_getcpuclockid 와 pthread_getcpuclockid 는 시계를 읽지 않습니다. 대신 그 프로세스나 스레드의 CPU 시간 시계를 가리키는 이름을 돌려줍니다. 그 이름을 clock_gettime 에 넘겨야 값이 나옵니다.
언어별로 이 시계를 읽는 함수
백엔드 코드는 clock_gettime 을 직접 부를 일이 드뭅니다. 언어마다 CPU 시간을 재는 함수를 따로 둡니다. 리눅스에서는 아래 함수들이 CLOCK_PROCESS_CPUTIME_ID 를 읽습니다.
| 언어 | 함수 |
|---|---|
| 파이썬 | time.process_time() |
| C | clock() |
C 의 clock() 은 리눅스의 C 표준 라이브러리 glibc 에서 이 시계를 읽습니다.
파이썬에는 스레드 하나의 CPU 시간을 재는 time.thread_time() 도 있습니다. 리눅스에서 이 함수는 CLOCK_THREAD_CPUTIME_ID 를 읽습니다.
쓰는 때와 안 쓰는 때
이 시계는 코드 한 덩이가 CPU 를 얼마나 먹는지 알고 싶을 때 씁니다. 함수마다 CPU 시간을 모아 보이는 프로파일러가 대표적입니다.
같은 기계에서 다른 프로그램이 함께 돌아도 이 시계의 값은 크게 흔들리지 않습니다. 코어를 뺏긴 시간이 빠지기 때문입니다. 그래서 바쁜 기계에서 코드 두 벌의 계산량을 견줄 때도 씁니다.
느린 요청의 까닭을 가를 때도 씁니다. 앞의 표처럼 걸린 시간과 함께 찍어 두면 계산 탓인지 기다림 탓인지가 로그 한 줄로 보입니다.
타이머의 기준으로도 쓸 수 있습니다. 타이머는 정해 둔 시간이 지나면 프로그램에 알려 주는 장치입니다. timer_create 에 이 이름을 넘기면 「이 프로세스가 CPU 를 10초 쓰면 알려 달라」는 타이머가 됩니다. 계산이 끝없이 도는 작업을 잡아낼 때 씁니다.
사용자가 느끼는 응답 시간은 이 시계로 재지 않습니다. 사용자는 기다린 시간까지 겪기 때문입니다. 응답 시간은 CLOCK_MONOTONIC 으로 잽니다.
타임아웃도 이 시계로 걸지 않습니다. 데이터베이스의 응답을 5초까지 기다리겠다는 기한을 이 시계로 잡았다고 해 보세요. 기다리는 동안 시계가 멈춰 있어서 5초가 영영 오지 않습니다.
지금이 몇 시인지도 이 시계로는 모릅니다. 날짜와 시각은 CLOCK_REALTIME 이 알려 줍니다.
관련 항목
CLOCK_PROCESS_CPUTIME_ID 와 나란히 선 운영체제 시계
CLOCK_THREAD_CPUTIME_ID · CLOCK_MONOTONIC · CLOCK_REALTIME · CLOCK_BOOTTIME · CLOCK_MONOTONIC_RAW · CLOCK_TAI
CLOCK_PROCESS_CPUTIME_ID 를 읽고 기준으로 삼는 호출
clock_gettime · clock_getres · clock_settime · clock_getcpuclockid · pthread_getcpuclockid · timer_create · clock_nanosleep
CLOCK_PROCESS_CPUTIME_ID 값을 담는 타입과 단위
struct timespec · clockid_t · time.h · 나노초
CLOCK_PROCESS_CPUTIME_ID 처럼 CPU 시간을 알려 주는 다른 수단
getrusage · times · clock · time.process_time · time.thread_time
CLOCK_PROCESS_CPUTIME_ID 가 세는 CPU 시간의 구성 요소
CPU 시간 · 사용자 시간 · 시스템 시간 · 사용자 모드 · 커널 모드 · 벽시계 시간
CLOCK_PROCESS_CPUTIME_ID 의 값을 늘리고 멈추는 커널 동작
스케줄링 · 문맥 교환 · CPU 코어 · 스레드 · 프로세스 · 락 · 블로킹 I-O
CLOCK_PROCESS_CPUTIME_ID 로 CPU 사용을 재는 작업
프로파일러 · 프로파일링 · 벤치마크 · CPU 사용률 · CPU 바운드 · I-O 바운드
CLOCK_PROCESS_CPUTIME_ID 가 속하는 상위 개념
시간과 시계 · CPU · 시스템 시계 · 벽시계 · 타이머
CLOCK_PROCESS_CPUTIME_ID 를 정하고 내주는 규격과 소프트웨어
다른 이름: clock_process_cputime_id · 프로세스 CPU 시간 시계 · 프로세스 CPU 시계