- Вопрос или проблема
- Связано:
- Ответ или решение
- 1. Использование встроенной команды time
- 2. Измерение времени с помощью переменных SECONDS
- 3. Для более точного измерения (в миллисекундах)
- 4. Использование команды times
- 5. Измерение времени на отдельных участках кода
- 6. Использование инструмента perf
- Заключение
Вопрос или проблема
Я хотел бы отобразить время выполнения скрипта.
То, что я сейчас делаю, – это –
#!/bin/bash
date ## выводим дату в начале
# содержимое скрипта
date ## выводим дату в конце
Это просто показывает время начала и окончания скрипта. Возможно ли отображать более детализированный вывод, такой как время процессора/время ввода-вывода и т.д.?
Просто используйте time
при вызове скрипта:
time yourscript.sh
Вывод (“# комментарии не являются частью вывода”):
real 2m5.034s # <-- Фактическое время, затраченное с начала до конца.
user 0m0.000s # <-- Время процессора в пользовательском пространстве.
sys 0m0.003s # <-- Время процессора в ядре.
Если time
– это не вариант,
start=`date +%s`
stuff
end=`date +%s`
runtime=$((end-start))
или, если вам нужна точность до субсекунд и у вас установлен bc
,
start=`date +%s.%N`
stuff
end=`date +%s.%N`
runtime=$( echo "$end - $start" | bc -l )
Просто вызовите times
без аргументов при выходе из вашего скрипта.
С ksh
или zsh
вы также можете использовать time
вместо. С zsh
time
также даст вам реальное время, помимо пользовательского и системного времени процессора.
Чтобы сохранить код завершения вашего скрипта, вы можете сделать так:
ret=$?; times; exit "$ret"
Или вы можете также добавить ловушку на EXIT
:
trap times EXIT
Таким образом, times будет вызываться каждый раз, когда оболочка завершается, и статус выхода будет сохранен.
$ bash -c 'trap times EXIT; : {1..1000000}'
0m0.932s 0m0.028s
0m0.000s 0m0.000s
$ zsh -c 'trap time EXIT; : {1..1000000}'
shell 0.67s user 0.01s system 100% cpu 0.677 total
children 0.00s user 0.00s system 0% cpu 0.677 total
Также обратите внимание, что все из bash
, ksh
и zsh
имеют специальную переменную $SECONDS
, которая автоматически увеличивается каждую секунду. В zsh
и ksh93
эта переменная также может быть сделана плавающей (с typeset -F SECONDS
), чтобы получить более точные значения. Это только реальное время, не время процессора.
Мой метод для bash
:
# Сброс счетчика времени BASH
SECONDS=0
#
# выполняем действия
#
ELAPSED="Elapsed: $(($SECONDS / 3600))hrs $((($SECONDS / 60) % 60))min $(($SECONDS % 60))sec"
Я немного запаздываю, но хотел бы опубликовать свое решение (для субсекундной точности), на случай если кто-то наткнется на эту тему через поиск. Вывод в формате дней, часов, минут и, наконец, секунд:
res1=$(date +%s.%N)
# выполняем действия здесь
res2=$(date +%s.%N)
dt=$(echo "$res2 - $res1" | bc)
dd=$(echo "$dt/86400" | bc)
dt2=$(echo "$dt-86400*$dd" | bc)
dh=$(echo "$dt2/3600" | bc)
dt3=$(echo "$dt2-3600*$dh" | bc)
dm=$(echo "$dt3/60" | bc)
ds=$(echo "$dt3-60*$dm" | bc)
LC_NUMERIC=C printf "Общее время выполнения: %d:%02d:%02d:%02.4f\n" $dd $dh $dm $ds
Надеюсь, это будет полезно кому-то!
[редактировать] Вам нужно учитывать все символы в определении поля в bash printf, если вы хотите дополнять секунды до 2 цифр перед точкой, вам нужно определить это как %07.4f
(все цифры и точка также считаются в длину поля), так что строка должна выглядеть так:
LC_NUMERIC=C printf "Общее время выполнения: %d:%02d:%02d:%07.4f\n" $dd $dh $dm $ds
Лично я люблю оборачивать весь код скрипта в какую-то “главную” функцию, вот так:
main () {
echo выполняется ...
}
# действия ...
# вызываем функцию в самом конце скрипта
time main
Обратите внимание, как легко использовать команду time
в этом сценарии. Очевидно, вы не измеряете точное время, включая время разбора скрипта, но для большинства ситуаций это достаточно точно.
Этот вопрос достаточно старый, но в попытках найти свой любимый способ сделать это, эта тема стала популярной… и я удивлён, что никто не упомянул об этом:
perf stat -r 10 -B sleep 1
‘perf’ – это инструмент анализа производительности, который включен в ядро под ‘tools/perf’ и часто доступен для установки как отдельный пакет (‘perf’ в CentOS и ‘linux-tools’ в Debian/Ubuntu). Вики Linux Kernal по perf содержит много другой информации об этом.
Запуск ‘perf stat’ дает довольно много деталей, включая среднее время выполнения прямо в конце:
1.002248382 seconds time elapsed ( +- 0.01% )
#!/bin/bash
start=$(date +%s.%N)
# ЗДЕСЬ БУДЕТ КОД
end=$(date +%s.%N)
runtime=$(python -c "print(${end} - ${start})")
echo "Время выполнения составило $runtime"
Да, это вызывает Python, но если вас это устраивает, то это довольно неплохое, лаконичное решение.
Небольшая функция оболочки, которую можно добавить перед командами для измерения их времени:
#!/bin/bash
tm_date() {
local start
start=$(date +%s)
"$@"
local exit_code=$?
echo >&2 "потратил ~$(($(date +%s) - start)) секунд. завершился с кодом ${exit_code}."
return ${exit_code}
}
tm_secs() {
local start=$EPOCHSECONDS
"$@"
local exit_code=$?
echo >&2 "потратил ~$((EPOCHSECONDS - start)) секунд. завершился с кодом ${exit_code}."
return ${exit_code}
}
tm_millisecs() {
local start=${EPOCHREALTIME/./}
"$@"
local exit_code=$?
echo >&2 "потратил ~$(( (${EPOCHREALTIME/./} - start)/1000 )) миллисекунд. завершился с кодом ${exit_code}."
return ${exit_code}
}
tm_microsecs() {
local start=${EPOCHREALTIME/./}
"$@"
local exit_code=$?
echo >&2 "потратил ~$((${EPOCHREALTIME/./} - start)) микросекунд. завершился с кодом ${exit_code}."
return ${exit_code}
}
Затем используйте это в своем скрипте или в командной строке, вот так:
tm original_command со всеми его параметрами
Например,
tm_date sleep 1
tm_secs sleep 1
tm_millisecs sleep 1
tm_microsecs sleep 1
Вывод будет таким:
потратил ~1 секунд. завершился с кодом 0.
потратил ~1 секунд. завершился с кодом 0.
потратил ~1002 миллисекунд. завершился с кодом 0.
потратил ~1001540 микросекунд. завершился с кодом 0.
Принятое решение с использованием time
записывает в stderr
.
Решение с использованием times
записывает в stdout
.
Решения с использованием $SECONDS
не имеют субсекундной точности.
Другие решения предполагают вызов внешних программ, таких как date
или perf
, что неэффективно.
Если вас устраивает любое из этих, используйте это.
Но если вам нужно эффективное решение, чтобы получить времена с миллисекундной точностью и нужно, чтобы они были в переменных таким образом, что оригинальный вывод остается нетронутым, вы можете объединить подстановку процессов с некоторыми перенаправлениями вокруг time
, что намного быстрее, чем вызов внешних программ и позволяет перенаправления вокруг обертки времени скрипта, как в оригинальной команде/скрипте.
# Подготовка:
Cmd=vgs # пример программы, которую нужно замерить, которая записывает в stdout и stderr
Cmd="eval { echo stdout; echo stderr >&2; sleep 0.1; }" # другой пример; замените на свой
TIMEFORMAT="%3R %3U %3S" # сделать вывод времени легко для парсинга
Выберите один из следующих вариантов анализа вывода time
, подходящий для ваших нужд:
Самый короткий вариант, где stdout
$Cmd
записывается в stderr
и ничего не записывается в stdout
:
read Elapsed User System < <({ time $Cmd 2>&3; } 3>&2 2>&1 >&3)
Длиннее вариант, который сохраняет оригинальный stdout
и stderr
отдельно:
{ read Elapsed User System < <({ time $Cmd 2>&4; } 4>&2 2>&1 >&3); } 3>&1
Самый сложный вариант включает закрытие дополнительных файловых дескрипторов, так что $Cmd
вызывается, как если бы не было этой оберточной функции замера времени вокруг него, и команды lvm
, такие как vgs
, не жаловались бы на утечку файловых дескрипторов:
{ read Elapsed User System < <({ time $Cmd 2>&4 4>&-; } 4>&2 2>&1 >&3 3>&-); } 3>&1
Вы даже можете подделать плавающее дополнение в bash
без вызова bc
, что было бы значительно медленнее:
CPU=`printf %04d $((10#${User/.}+10#${System/.}))` # замените на вашу последующую обработку
echo CPU ${CPU::-3}.${CPU: -3} s, Elapsed $Elapsed s >&2 # перенаправлено независимо от $Cmd
Возможные выводы с двумя примерами $Cmd
на медленном процессоре:
Файловый дескриптор 3 (/dev/pts/1) утек на вызове vgs. Основной PID 10756: bash
Файловый дескриптор 4 (/dev/pts/1) утек на вызове vgs. Основной PID 10756: bash
VG #PV #LV #SN Attr VSize VFree
b3 3 24 0 wz--n- 1.31t 1.19t
CPU 0.052 s, Elapsed 0.056 s
Или:
stdout
stderr
CPU 0.008 s, Elapsed 0.109 s
-
Просто используйте
time [любая команда]
. Например:time sleep 1
будет ожидать реальное время (т.е. как по часам) ~1.000 до ~1.020 сек, как показано здесь:$ time sleep 1 real 0m1.011s user 0m0.004s sys 0m0.000s
Какая прекрасная вещь. Вы можете поставить любую команду после этого, и она выводит результат в приятном, читаемом виде. Я действительно люблю использовать это для измерения времени сборок. Например:
# измеряем время вашей сборки "make" time make # измеряем время вашей сборки "Bazel" time bazel build //path/to/some:target
…или для операций git, которые потенциально могут быть действительно долгими, чтобы я мог развивать реалистичные ожидания:
# измеряем, сколько времени потребуется для извлечения из огромного репозитория, когда # я работаю из дома во время COVID-19. Обратите внимание: `git pull` # гораздо медленнее, чем просто извлечение одной ветки # которая вам нужна с помощью `git pull origin
`, поэтому просто извлекайте # или затаскивайте, что вам нужно! time git pull origin master -
Для более настроенных нужд в измерении времени, когда вам может потребоваться манипулировать выводом или преобразовывать его в другие формы, в bash используйте внутреннюю переменную
$SECONDS
. Вот демонстрация, включая преобразование этих секунд в другие единицы, такие как плавающие минуты:Обратите внимание, что
dt_min
округляется с0.01666666666...
(1 секунда = столько минут) до0.017
в этом случае, поскольку я использую функциюprintf
для округления. Частиsleep 1;
ниже – это то место, где вы вызовете свой скрипт для выполнения и измерения, но я просто жду 1 секунду вместо этого ради этого демонстрационного примера.Команда:
start=$SECONDS; sleep 1; end=$SECONDS; dt_sec=$(( end - start )); \ dt_min=$(printf %.3f $(echo "$dt_sec/60" | bc -l)); \ echo "dt_sec = $dt_sec; dt_min = $dt_min"
Вывод:
dt_sec = 1; dt_min = 0.017
Связано:
- Узнайте больше о
bc
иprintf
в моем ответе здесь: https://stackoverflow.com/questions/12722095/how-do-i-use-floating-point-division-in-bash/58479867#58479867 - Я не помню, где впервые узнал о команде
time
, но, возможно, это было в @Trudbert’s answer right here.
#!/bin/csh
#PBS -q glean
#PBS -l nodes=1:ppn=1
#PBS -l walltime=10:00:00
#PBS -o a.log
#PBS -e a.err
#PBS -V
#PBS -M [email protected]
#PBS -m abe
#PBS -A k4zhang-group
START=$(date +%s)
for i in {1..1000000}
do
echo 1
done
END=$(date +%s)
DIFF=$(echo "$END - $START" | bc)
echo "Это занимает DIFF=$DIFF секунд, чтобы завершить эту задачу..."
Использовать встроенную команду времени bash?
time: time [-p] PIPELINE
Выполняет PIPELINE и выводит сводку реального времени, пользовательского времени CPU,
и времени системного CPU, затраченного на выполнение PIPELINE после его завершения.
Код возврата - это код возврата PIPELINE. Опция `-p' выводит
сводку времени в немного другом формате. Это использует
значение переменной TIMEFORMAT как формат вывода.
Пример:
TIMEFORMAT="Команда заняла %Rs"
time {
sleep 0.1
}
Вывод:
Команда заняла 0.108s
С использованием только bash также возможно измерить и рассчитать время выполнения для части shell-скрипта (или прошедшее время для всего скрипта):
start=$SECONDS
... # выполняем времязатратные действия
end=$SECONDS
Теперь вы можете просто вывести разницу:
echo "длительность: $((end-start)) секунд."
если вам нужно только инкрементальное время выполнения, делайте:
echo "длительность: $((SECONDS-start)) секунд прошло.."
Вы также можете сохранить длительность в переменную:
let diff=end-start
Функция измерения времени, основанная на SECONDS
, ограничена только секундным уровнем точности, не использует никаких внешних команд:
time_it() {
local start=$SECONDS ts ec
printf -v ts '%(%Y-%m-%d_%H:%M:%S)T' -1
printf '%s\n' "$ts Запущено $*"
"$@"; ec=$?
printf -v ts '%(%Y-%m-%d_%H:%M:%S)T' -1
printf '%s\n' "$ts Завершено $*; прошедшее время = $((SECONDS-start)) секунд"
# убедитесь, что возврат кода команды, чтобы вызывающий мог использовать его
return "$ec"
}
Например:
time_it sleep 5
выводит
2019-03-30_17:24:37 Запущено sleep 5 2019-03-30_17:24:42 Завершено
sleep 5; прошедшее время = 5 секунд
Еще одно встроенное решение (согласно: https://stackoverflow.com/a/385422/1350091) это:
/usr/bin/time -v command
#!/bin/bash
begin=$(date +"%s")
Скрипт
termin=$(date +"%s")
difftimelps=$(($termin-$begin))
echo "$(($difftimelps / 60)) минут и $(($difftimelps % 60)) секунд прошло для выполнения скрипта."
похожие на ответ @LeZuse, но поддерживают аргументы скрипта:
#!/bin/bash
main () {
echo "мой первый аргумент это $1"
# содержимое скрипта здесь
}
# "$@" будет развернут до аргументов скрипта
time main "$@"
Альтернатива, если вы хотите измерить несколько частей скрипта:
#!/bin/bash
mts=$(date +%s%3N);mtl=$mts
sleep 1
mtc=$(date +%s%3N);printf "строка $LINENO: %.3fs [+%.3fs]\\n" "$((mtc - mts))e-3" "$((mtc - mtl))e-3";mtl=$mtc
sleep 1.5
mtc=$(date +%s%3N);printf "строка $LINENO: %.3fs [+%.3fs]\\n" "$((mtc - mts))e-3" "$((mtc - mtl))e-3";mtl=$mtc
вывод:
строка 5: 1.007s [+1.007s]
строка 8: 2.514s [+1.507s]
Таким образом, он возвращает общее время выполнения и время с последней измеренной строки.
Вот вариант ответа Алекса. Я заботился только о минутах и секундах, но я также хотел, чтобы он был оформлен по-другому. Так что я сделал так:
start=$(date +%s)
end=$(date +%s)
runtime=$(python -c "print '%u:%02u' % ((${end} - ${start})/60, (${end} - ${start})%60)")
Чтобы вычислить прошедшее время для bash-скрипта в миллисекундах:
start_time=$(date +%s%3N) # время начала в миллисекундах
sleep 1 # <-- код для замера здесь
end_time=$(date +%s%3N) # # время окончания в миллисекундах
duration_ms=$((end_time - start_time)) # длительность в миллисекундах
echo "Время выполнения в мс: $duration_ms"
Проверено в bash версии 5.0.17.
Ссылки
- Как получить текущее время Unix в миллисекундах в Bash?
- Измерение прошедшего времени в миллисекундах в bash
В bash версии 3, как описано здесь, могут быть проблемы:
С bash V3 команда $(date +%s%N) вернет что-то вроде
1692824647N: и когда вы попытаетесь вычислить разницу, вы
получите 1692824647N: значение слишком велико для базовой ошибки. В bash V4
эта проблема решена, но если вы все еще на bash 3 и хотите
преодолеть эту проблему, лучше использовать
/usr/local/gnu/coreutils/bin/date библиотеку от GNU coreutils.
Ответ или решение
Для измерения времени выполнения скрипта в оболочке Bash существует несколько эффективных методов, которые позволяют вам получить как общее время выполнения, так и более детальные показатели, такие как время процессора и ввод-вывод. Вот несколько наиболее распространенных подходов.
1. Использование встроенной команды time
Самый простой способ измерить время выполнения скрипта – это запустить его с помощью команды time
:
time ./your_script.sh
Это выведет три значения:
real
: фактическое время выполнения (стена).user
: время, проведенное в пользовательском пространстве (использование CPU для выполнения процесса).sys
: время, проведенное в ядре (системные вызовы).
Вывод будет примерно таким:
real 0m1.034s
user 0m0.004s
sys 0m0.000s
2. Измерение времени с помощью переменных SECONDS
Переменная SECONDS
автоматически увеличивается на 1 каждую секунду. Вы можете использовать ее для измерения времени выполнения:
SECONDS=0 # Сброс счётчика
# Ваш код
echo "Время выполнения: $SECONDS секунд."
Это простой способ, но он имеет ограничение по точности (измеряет только целые секунды).
3. Для более точного измерения (в миллисекундах)
Если вам нужна большая точность, вы можете использовать команду date
:
start=$(date +%s.%N) # Время начала с наносекундами
# Ваш код
end=$(date +%s.%N) # Время окончания
runtime=$(echo "$end - $start" | bc) # Разница
echo "Время выполнения: $runtime секунд."
4. Использование команды times
Вы также можете использовать встроенную команду times
, которая предоставит общую информацию о времени процессора:
# Здесь выполняются ваши команды
times
Когда вы вызываете эту команду, вы получите информацию о процессе, включая время, проведённое в пользовательском пространстве и в ядре.
5. Измерение времени на отдельных участках кода
Если вам нужно замерять производительность на определённых участках, вы можете создать свою функцию:
time_it() {
local start=$(date +%s.%N)
"$@"
local exit_code=$?
local end=$(date +%s.%N)
local runtime=$(echo "$end - $start" | bc)
echo "Команда '$*' завершилась за $runtime секунд."
return $exit_code
}
# Пример использования
time_it sleep 1
6. Использование инструмента perf
Для более глубокого анализа производительности вы можете использовать инструмент perf
, который предоставляет детальную информацию о времени выполнения и производительности:
perf stat ./your_script.sh
Заключение
В зависимости от ваших требований к точности и детализации, вы можете выбрать один из предложенных методов. Для большинства задач простой time
будет достаточен, тогда как для более сложных сценариев анализа может потребоваться использование date
, times
или специализированных инструментов вроде perf
.