프로그램이 실행되는 데 소요되는 시간과 리소스 사용량을 정확히 파악하고 싶으신가요? Linux의 time 명령어는 시간 통계를 제공하여 프로그램의 성능에 대한 귀중한 정보를 제공합니다.
다양한 쉘 환경과 Time 명령어
Linux는 여러 배포판과 다양한 Unix 계열 운영 체제를 가지고 있습니다. 이러한 운영 체제들은 각각 고유의 기본 명령 쉘을 제공합니다. 최신 Linux 배포판에서 가장 많이 사용되는 쉘은 Bash 쉘이지만, Zsh나 Ksh와 같은 다른 쉘들도 존재합니다.
흥미로운 점은, 이러한 모든 쉘들이 자체적인 time 명령어를 내장하고 있다는 것입니다. 이는 쉘 내부에 포함된 명령 또는 예약어로 구현되어 있습니다. 따라서 터미널에서 “time”을 입력하면, 쉘은 Linux 배포판의 일부인 GNU time 바이너리를 사용하는 대신 자체 내부 명령을 실행하게 됩니다.
하지만 우리는 더 다양한 옵션을 제공하는 GNU 버전의 time 명령어를 사용하고 싶습니다. GNU time은 더욱 유연한 분석을 가능하게 합니다.
어떤 Time 명령어를 실행하고 있을까?
type 명령어를 사용하여 실제로 실행되는 time 명령어의 버전을 확인할 수 있습니다. type 명령어는 쉘이 자체 내부 루틴을 처리할지, 아니면 GNU 바이너리를 사용할지 알려줍니다.
터미널에서 “type time”을 입력하고 Enter 키를 누르면 결과를 확인할 수 있습니다.
type time
위의 예시에서 Bash 쉘은 time을 예약어로 취급하는 것을 볼 수 있습니다. 이는 Bash가 기본적으로 자체 내부 time 루틴을 사용한다는 것을 의미합니다.
type time
Zsh에서도 time은 예약어이므로, 기본적으로 내부 쉘 루틴을 사용합니다.
type time
Ksh 쉘에서도 time은 키워드로 취급되어 내부 루틴이 사용됩니다. 즉, 기본적으로 GNU time 명령어가 아닌 쉘의 내부 시간 측정 기능이 사용되는 것입니다.
GNU Time 명령어 실행 방법
Linux 시스템의 쉘이 내부 time 루틴을 가지고 있을 경우, GNU time 바이너리를 사용하려면 명시적으로 지정해야 합니다. 다음 방법 중 하나를 사용할 수 있습니다.
/usr/bin/time
과 같이 바이너리의 전체 경로를 입력합니다. 이 경로는which time
명령을 통해 찾을 수 있습니다.command time
명령을 사용합니다.- 백슬래시를 사용하여
\time
과 같이 입력합니다.
which time
명령은 GNU time 바이너리의 정확한 경로를 보여줍니다.
/usr/bin/time
을 명령으로 사용하여 GNU 바이너리가 실행되는지 확인해 볼 수 있습니다. 명령 자체는 잘 작동하지만, time 명령어에 대한 명령줄 매개변수가 없어 사용법에 대한 정보를 반환합니다.
command time
명령어 역시 동일하게 작동하며, time에 대한 사용법 정보를 출력합니다. command
명령은 쉘에게 해당 명령을 쉘 외부에서 처리하도록 지시합니다.
명령 이름 앞에 백슬래시를 사용하는 것은 command
명령을 사용하는 것과 동일한 효과를 줍니다.
가장 간단하게 GNU time 바이너리를 사용하는 방법은 백슬래시 옵션을 사용하는 것입니다.
\time
time
윗줄의 time
은 쉘 버전의 time을 호출하고, 아랫줄의 \time
은 time 바이너리를 사용합니다.
Time 명령어 활용 예시
이제 실제 프로그램을 대상으로 time 명령어를 사용해 보겠습니다. 여기서는 loop1
과 loop2
라는 두 가지 간단한 프로그램을 사용합니다. 이 프로그램들은 loop1.c
와 loop2.c
파일에서 각각 생성되었으며, 코드 비효율이 성능에 미치는 영향을 보여주는 예시입니다.
loop1.c
코드는 다음과 같습니다. 중첩된 두 개의 루프 안에서 문자열의 길이를 필요로 하지만, 이 길이는 루프 바깥에서 미리 계산됩니다.
#include "stdio.h" #include "string.h" #include "stdlib.h" int main (int argc, char* argv[]) { int i, j, len, count=0; char szString[]="how-to-geek-how-to-geek-how-to-geek-how-to-geek-how-to-geek-how-to-geek"; // get length of string once, outside of loops len = strlen( szString ); for (j=0; j<1000; j++) for (i=0; i<10000; i++) count++; printf("count=%d\n",count); return 0; }
loop2.c
코드는 다음과 같습니다. 문자열의 길이를 외부 루프의 각 반복마다 계속 계산합니다. 이 비효율성은 time 명령어의 결과에 반영될 것입니다.
#include "stdio.h" #include "string.h" #include "stdlib.h" int main (int argc, char* argv[]) { int i, j, count=0; char szString[]="how-to-geek-how-to-geek-how-to-geek-how-to-geek-how-to-geek-how-to-geek"; for (j=0; j<1000; j++) for (i=0; i<10000; i++) strlen( szString ); count++; printf("count=%d\n",count); return 0; }
이제 loop1
프로그램을 실행하고 time을 사용하여 성능을 측정해 보겠습니다.
time ./loop1
같은 방식으로 loop2
프로그램도 실행해 보겠습니다.
time ./loop2
결과가 두 세트 출력되었지만, 형식이 다소 보기 불편합니다. 여기에서 몇 가지 정보를 추려내겠습니다.
프로그램이 실행될 때 사용자 모드와 커널 모드의 두 가지 실행 모드로 전환됩니다.
간단히 말해, 사용자 모드의 프로세스는 자체 할당 영역 외의 하드웨어나 메모리에 직접 접근할 수 없습니다. 이러한 리소스에 접근하려면 프로세스는 커널에 요청해야 합니다. 커널이 요청을 승인하면, 프로세스는 요구 사항이 충족될 때까지 커널 모드에서 실행됩니다. 그 후 프로세스는 다시 사용자 모드 실행으로 전환됩니다.
loop1
의 결과에 따르면, loop1
은 사용자 모드에서 0.09초를 보냈습니다. 커널 모드에서는 0초를 보냈거나, 시간이 너무 짧아 기록되지 않았습니다. 총 경과 시간은 0.1초였습니다. loop1
은 총 경과 시간 동안 평균 89%의 CPU 시간을 사용했습니다.
비효율적인 loop2
프로그램은 실행하는 데 3배 더 오래 걸렸습니다. 총 경과 시간은 0.3초이고, 사용자 모드에서 0.29초를 보냈습니다. 커널 모드에는 기록된 시간이 없습니다. loop2
는 실행 시간 동안 평균 96%의 CPU 시간을 사용했습니다.
출력 형식 지정하기
형식 문자열을 사용하여 time 명령의 출력을 사용자 정의할 수 있습니다. 형식 문자열은 텍스트와 형식 지정자를 포함할 수 있습니다. 형식 지정자 목록은 time 명령어의 man 페이지에서 확인할 수 있습니다. 각 형식 지정자는 특정 정보를 나타냅니다.
문자열이 출력될 때, 형식 지정자는 해당 값으로 대체됩니다. 예를 들어, CPU 사용률을 나타내는 형식 지정자는 P
입니다. 형식 지정자가 일반 문자가 아님을 나타내기 위해 %P
와 같이 퍼센트 기호를 추가합니다. 이를 실제 예시에서 사용해 보겠습니다.
-f
(형식 문자열) 옵션은 time 명령어에게 다음에 오는 내용이 형식 문자열임을 알리는 데 사용됩니다.
형식 문자열은 “Program:” 문자열과 프로그램 이름(및 프로그램에 전달된 명령줄 인자)을 출력합니다. %C
형식 지정자는 “시간이 측정되는 명령의 이름과 명령줄 인자”를 나타냅니다. \n
은 출력을 다음 줄로 넘깁니다.
형식 지정자는 대소문자를 구분하므로, 직접 사용할 때 올바르게 입력해야 합니다.
다음으로 “Total time:” 문자열을 출력하고, 이 프로그램 실행의 총 경과 시간 값(%E
로 표시)을 출력합니다.
\n
을 사용하여 또 다른 줄바꿈을 추가합니다. 그 다음 “User Mode(s)” 문자열을 출력하고, %U
로 표시되는 사용자 모드에서 소요된 CPU 시간 값을 출력합니다.
다시 \n
을 사용하여 새 줄을 추가하고, 이번에는 커널 시간 값에 대한 준비를 합니다. “Kernel Mode(s)” 문자열을 출력하고 그 뒤에 커널 모드에서 소요된 CPU 시간 형식 지정자인 %S
를 출력합니다.
마지막으로, 이 데이터 값에 대한 새 줄과 제목을 제공하기 위해 “CPU:” 문자열을 출력합니다. %P
형식 지정자는 시간이 측정된 프로세스에서 사용한 평균 CPU 시간 비율을 제공합니다.
전체 형식 문자열은 따옴표로 묶입니다. 값 정렬이 어렵다면, 탭을 추가하기 위해 \t
를 사용할 수도 있습니다.
time -f "Program: %C\nTotal time: %E\nUser Mode (s) %U\nKernel Mode (s) %S\nCPU: %P" ./loop1
출력을 파일로 리다이렉션
실행한 테스트의 시간 데이터를 기록하기 위해 출력을 파일로 보낼 수 있습니다. 이를 위해 -o
(출력) 옵션을 사용합니다. 프로그램의 출력은 여전히 터미널 창에 표시됩니다. 파일로 리다이렉션되는 것은 time 명령의 출력뿐입니다.
테스트를 다시 실행하고, 다음 명령을 사용하여 출력을 test_results.txt
파일에 저장할 수 있습니다.
time -o test_results.txt -f "Program: %C\nTotal time: %E\nUser Mode (s) %U\nKernel Mode (s) %S\nCPU: %P" ./loop1
cat test_results.txt
loop1
프로그램의 출력은 터미널 창에 표시되고, time 명령어의 결과는 test_results.txt
파일로 저장됩니다.
동일한 파일에 다음 결과 세트를 추가하려면 -a
(추가) 옵션을 사용해야 합니다.
time -o test_results.txt -a -f "Program: %C\nTotal time: %E\nUser Mode (s) %U\nKernel Mode (s) %S\nCPU: %P" ./loop2
cat test_results.txt
이제 형식 문자열 출력에 프로그램 이름을 포함하기 위해 %C
형식 지정자를 사용한 이유가 분명해졌습니다.
결론
time 명령어는 프로그래머와 개발자가 코드를 최적화하는 데 매우 유용하며, 프로그램이 실행될 때 내부적으로 어떤 일이 일어나는지 알고 싶어하는 모든 사람에게도 도움이 됩니다.