Бажаєте дізнатися тривалість виконання програм та інші деталі? Команда Linux `time` надає статистичні дані про час, допомагаючи вам зрозуміти, як ваші програми використовують ресурси.
Команда `time` та її аналоги
Існує багато дистрибутивів Linux та різноманітних Unix-подібних операційних систем, кожна з яких має свою оболонку командного рядка за замовчуванням. У сучасних дистрибутивах Linux найпоширенішою є оболонка bash, але є й інші, наприклад, zsh та ksh.
Всі ці оболонки мають власну команду `time`, реалізовану або як вбудовану команду, або як зарезервоване слово. Коли ви вводите `time` у терміналі, оболонка виконує свою внутрішню команду замість використання бінарного файлу GNU time, який є частиною дистрибутиву Linux.
Нам потрібна саме версія GNU time, оскільки вона має розширений набір параметрів та більшу гнучкість.
Яка версія `time` буде запущена?
Щоб дізнатися, яка версія `time` буде використовуватися, застосуйте команду `type`. Вона покаже, чи обробляє оболонка інструкції самостійно, використовуючи свої вбудовані підпрограми, чи передає їх до бінарного файлу GNU.
Введіть у терміналі: `type time` і натисніть Enter.
type time
Як бачимо, в оболонці bash `time` є зарезервованим словом. Це означає, що bash за замовчуванням використовуватиме свої внутрішні підпрограми.
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` показує шлях до бінарного файлу.
Щоб перевірити, чи працює команда, використаємо `/usr/bin/time` для запуску бінарного файлу GNU. Це працює, і ми отримуємо відповідь від `time` про відсутність параметрів командного рядка.
Введення `command time` також працює, і ми отримуємо ту саму інформацію. Команда `command` каже оболонці ігнорувати наступну команду, щоб вона оброблялася за межами оболонки.
Використання символу `\` перед назвою команди має той самий ефект, що й використання команди `command`.
Найпростіший спосіб використовувати саме бінарний файл GNU `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Це код `loop2.c`. Довжина рядка отримується на кожній ітерації зовнішнього циклу. Ця неефективність має відобразитися на часі виконання.
#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Запустимо програму `loop1` та виміряємо її продуктивність за допомогою `time`.
\time ./loop1
Тепер зробимо те саме для `loop2`.
\time ./loop2
Ми отримали два набори результатів у незручному форматі. Пізніше ми зможемо це виправити, а зараз виберемо декілька частин інформації.
Під час виконання програми існують два режими роботи: режим користувача та режим ядра. Процес у режимі користувача не може напряму звертатися до апаратного забезпечення або керувати пам'яттю за межами власного розподілу. Для доступу до таких ресурсів процес звертається до ядра. Якщо ядро схвалює запит, процес переходить до виконання в режимі ядра, поки вимога не буде виконана. Після цього процес повертається до режиму користувача.
Згідно з результатами, `loop1` провела 0.09 секунд у режимі користувача. Час у режимі ядра дорівнює нулю або є занадто малим для реєстрації після округлення. Загальний час виконання склав 0.1 секунди. Програма `loop1` використовувала в середньому 89% часу процесора протягом всього часу виконання.
Виконання неефективної програми `loop2` зайняло втричі більше часу: 0.3 секунди. Час обробки в режимі користувача склав 0.29 секунди, а в режимі ядра – нуль. Програма `loop2` використовувала в середньому 96% часу процесора протягом свого виконання.
Форматування виводу
Ви можете налаштувати вивід команди `time` за допомогою рядка форматування. Рядок форматування може включати текст та специфікатори формату. Список специфікаторів формату можна знайти на сторінці man для `time`. Кожен специфікатор формату представляє певну частину інформації.
Коли рядок виводиться, специфікатори формату замінюються фактичними значеннями. Наприклад, специфікатор формату для відсотка використання процесора – це літера `P`. Щоб показати команді `time`, що це специфікатор формату, потрібно додати знак відсотка, наприклад, `%P`. Розглянемо це на прикладі.
Параметр `-f` (рядок форматування) повідомляє команді `time`, що далі йде рядок форматування.
Наш рядок форматування виведе символи "Програма:", а потім назву програми (і будь-які параметри командного рядка). Специфікатор формату `%C` означає "Ім'я та аргументи командного рядка команди, що вимірюється". `\n` переносить результат на новий рядок.
Існує багато специфікаторів формату, вони чутливі до регістру, тому переконайтеся, що ви вводите їх правильно.
Далі ми виведемо символи "Загальний час:", а потім значення загального часу, що минув для виконання програми (представлене `%E`).
Ми використовуємо `\n` для створення ще одного нового рядка. Потім ми виведемо символи "Режим користувача (с)", а потім значення часу процесора, проведеного в режимі користувача, позначене `%U`.
Використовуємо `\n` ще раз, щоб створити новий рядок. Цього разу ми виведемо значення часу ядра. Ми друкуємо символи "Режим ядра (с)", а потім специфікатор формату для часу процесора в режимі ядра, тобто `%S`.
Нарешті, виведемо символи `\nCPU:` для створення нового рядка і назви для значення даних. Специфікатор формату `%P` надасть середній відсоток часу процесора, який використовувався процесом.
Весь рядок форматування вкладено в лапки. Ми могли б використовувати `\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" ./loop1cat 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" ./loop2cat test_results.txt
Тепер зрозуміло, чому ми використовували специфікатор формату `%C`, щоб включити назву програми у вивід з рядка форматування.
Підсумок
Команда `time` є надзвичайно корисною для програмістів та розробників для точного налаштування їх коду. Також вона може бути цікавою для тих, хто хоче дізнатися більше про те, що відбувається за лаштунками кожного разу, коли запускається програма.