Devin.KR
로그인

리눅스 time 명령어 사용법 - 셸 빌트인과 /usr/bin/time 차이, real user sys 읽는 법

개발자 조회 1

이 명령어를 언제 쓰는가

배치가 어제보다 느려졌다는 얘기를 들었을 때, 두 가지 구현 중 어느 쪽이 빠른지 확인할 때, 컨테이너 메모리 리밋을 얼마로 잡아야 할지 감을 잡을 때 time을 앞에 붙인다.

여기서 반드시 짚고 갈 것이 하나 있다. time은 두 개다. 하나는 bash·zsh에 내장된 셸 예약어이고, 다른 하나는 /usr/bin/time이라는 별도 실행 파일이다. 이름이 같아서 늘 헷갈리는데, 할 수 있는 일이 꽤 다르다. 메모리 사용량이 궁금하면 후자를 써야 한다.

기본 형식

time 명령 [인자...]              # 셸 예약어
/usr/bin/time [옵션] 명령 [인자...]   # 별도 바이너리

차이를 표로 정리하면 이렇다.

구분셸 예약어 time/usr/bin/time
정체bash·zsh의 예약어. type timereserved word라고 답한다실행 파일. Debian·Ubuntu는 time, RHEL은 time 패키지
파이프라인 측정가능. time a | b가 전체를 잰다불가. 첫 명령만 잰다
셸 함수·빌트인 측정가능불가(실행 파일이 아니라서)
메모리·페이지폴트불가가능(-v, BSD는 -l)
출력 서식 변경TIMEFORMAT(bash), TIMEFMT(zsh)-f 옵션
파일로 저장{ time 명령; } 2> 파일-o 파일

출력 읽는 법

bash에서 time sleep 0.2를 실행하면 이렇게 나온다.

real	0m0.206s
user	0m0.001s
sys	0m0.002s
  • real — 벽시계 시간. 시작부터 끝까지 실제로 흐른 시간이다. 사용자가 체감하는 시간이 이것이다.
  • user — 사용자 모드에서 CPU를 쓴 시간. 연산·문자열 처리 같은 순수 계산이 여기 들어간다.
  • sys — 커널 모드에서 CPU를 쓴 시간. 파일 읽기·쓰기, 소켓, 메모리 할당 등 시스템 콜 비용이다.

해석의 핵심은 real과 user+sys의 관계다. real이 훨씬 크고 user+sys가 거의 0이면 CPU는 놀고 있었다는 뜻이므로 디스크·네트워크·락 대기를 의심한다. 반대로 user+sys가 real보다 크면 여러 코어를 동시에 쓴 것이다. 8코어에서 real 10초에 user 70초가 나오면 병렬화가 잘 되고 있다는 신호다.

자주 쓰는 옵션(/usr/bin/time)

옵션의미예시
-vGNU. 최대 메모리, 페이지폴트, 컨텍스트 스위치까지 전부/usr/bin/time -v ./batch.sh
-f 서식필요한 항목만 골라 한 줄로/usr/bin/time -f '%e %M' ./batch.sh
-o 파일측정 결과를 파일에 저장/usr/bin/time -o t.log ./batch.sh
-a-o와 함께. 덮어쓰지 않고 이어쓰기/usr/bin/time -a -o t.log ./batch.sh
-lBSD·macOS. GNU의 -v에 해당/usr/bin/time -l ./batch.sh

-f 서식에서 자주 쓰는 것은 %e(real 초), %U(user), %S(sys), %M(최대 상주 메모리 KB), %P(CPU 사용률), %x(종료 코드)다.

실전 예제

1) 파이프라인 전체 시간을 잰다. 이건 셸 예약어만 할 수 있다. /usr/bin/time을 쓰면 zcat만 측정된다.

time zcat access.log.gz | grep ' 500 ' | wc -l

2) 배치의 최대 메모리 사용량을 확인한다. 컨테이너 memory.limit이나 JVM 힙 크기를 정하기 전에 실측하는 용도다. GNU time의 Maximum resident set size가 KB 단위로 나온다.

/usr/bin/time -v ./nightly_aggregate.sh 2> timing.txt
grep -E 'Elapsed|Maximum resident' timing.txt

3) 매일 실행 시간을 한 줄씩 쌓아 추세를 본다. 크론 잡이 서서히 느려지는 것을 잡아내는 가장 싼 방법이다.

/usr/bin/time -a -o /var/log/batch_timing.log \
  -f "$(date +%F) elapsed=%e user=%U sys=%S maxrss=%MKB exit=%x" \
  ./nightly_aggregate.sh

4) 두 구현을 비교한다. 캐시 영향을 줄이려면 순서를 바꿔 한 번씩 더 돌린다. 첫 실행은 페이지 캐시가 비어 있어 항상 느리므로 버린다.

time ./sort_v1.sh > /dev/null
time ./sort_v2.sh > /dev/null

5) 여러 명령을 묶어서 재고, 결과만 파일에 남긴다. 중괄호 블록과 2>의 조합이 필요하다.

{ time { ./extract.sh && ./transform.sh && ./load.sh ; } ; } 2> /tmp/etl_time.txt

6) 출력 서식을 바꾼다. bash는 TIMEFORMAT, zsh는 TIMEFMT다.

TIMEFORMAT='%3R real / %3U user / %3S sys'
time sleep 0.15
0.155 real / 0.001 user / 0.001 sys

함정과 주의점

time -v는 셸 예약어에서 동작하지 않는다. 가장 흔한 실수다. bash에서 time -v ./job.sh를 치면 셸이 -v를 명령 이름으로 해석해서 이런 결과가 나온다.

bash: -v: command not found

real	0m0.006s

실행 시간이 0.006초로 찍히니 명령이 엄청 빨랐다고 착각하기 쉽다. 실제로는 ./job.sh가 아예 실행되지 않았다. 해결책은 셋 중 하나다. /usr/bin/time -v로 절대 경로를 주거나, command time -v를 쓰거나, \time -v처럼 백슬래시를 붙인다.

출력이 표준 에러로 나간다. time ./job.sh > out.txt는 job.sh의 출력만 파일로 보내고 측정 결과는 화면에 남는다. 반대로 측정 결과만 파일로 보내려면 2>가 필요한데, 셸 예약어 time은 time ./job.sh 2> t.txt로도 잡히지 않는다. 그 2>는 job.sh에 붙기 때문이다. 반드시 { time ./job.sh; } 2> t.txt처럼 블록으로 감싸야 한다.

GNU와 BSD의 옵션이 다르다. 리눅스의 /usr/bin/time은 GNU time이라 -v-f를 쓰지만, macOS·FreeBSD의 것은 BSD time이라 -v가 없고 -l을 쓴다. macOS에서 /usr/bin/time -l sleep 0.1을 돌리면 이렇게 시작한다.

        0.10 real         0.00 user         0.00 sys
             1179648  maximum resident set size

여기서 최대 메모리는 바이트지만 GNU의 %MKB다. 스크립트를 두 환경에서 돌린다면 단위를 반드시 확인한다. macOS에서 GNU 쪽 동작이 필요하면 brew install gnu-timegtime을 쓴다.

/usr/bin/time이 없는 서버가 흔하다. 최소 설치 이미지나 컨테이너에는 time 패키지가 빠져 있는 경우가 많다. /usr/bin/time: No such file or directory가 뜨면 apt install time 또는 dnf install time으로 넣는다. 셸 예약어는 항상 있으므로 시간만 필요하면 그쪽으로 충분하다.

단 한 번 측정한 값을 믿지 마라. 페이지 캐시, 다른 프로세스의 부하, CPU 주파수 스케일링 때문에 같은 명령도 편차가 크다. 최소 3~5회 돌려 중앙값을 보고, 디스크 I/O가 관건이면 캐시 상태를 통일한 뒤 비교한다.

함께 보면 좋은 명령어

  • top — time은 끝난 뒤의 총합만 알려준다. 실행 중의 순간 CPU·메모리 변화는 top으로 본다.
  • psps -o etime,times로 이미 돌고 있는 프로세스의 누적 시간을 확인할 수 있다.
  • strace — sys 시간이 유독 크면 어떤 시스템 콜에서 잡아먹는지 strace -c로 집계해 본다.