it-swarm-ru.tech

Как измерить время выполнения программы и сохранить его в переменной

Чтобы выяснить, сколько времени занимают определенные операции в скрипте Bash (v4 +), я бы хотел проанализировать вывод команды time "по отдельности" и (в конечном итоге) перехватить его в переменной Bash (let VARNAME=...).

Теперь я использую time -f '%e' ... (или скорее command time -f '%e' ... из-за встроенного Bash), но так как я уже перенаправил вывод выполненной команды, я действительно заблудился относительно того, как я собираюсь захватить выходные данные команды time. В основном, проблема здесь в для отделения вывода of time от вывода выполненной команды.

То, что я хочу, это функциональность подсчета времени в секундах (целых) между началом команды и ее завершением. Это не обязательно должна быть команда time или соответствующая встроенная команда.


Edit: учитывая два полезных ответа ниже, я хотел бы добавить два разъяснения.

  1. Я не хочу отбрасывать вывод выполненной команды, но на самом деле не имеет значения, окажется ли он на stdout или stderr.
  2. Я бы предпочел прямой подход, а не косвенный (т. Е. Перехватывать вывод напрямую, а не сохранять его в промежуточных файлах).

Решение, использующее date, до сих пор подходит к тому, что я хочу.

65
0xC0000022L

Чтобы получить вывод time в переменную, используйте следующее:

[email protected] $ mytime="$(time ( ls ) 2>&1 1>/dev/null )"
[email protected] $ echo "$mytime"

real    0m0.006s
user    0m0.001s
sys     0m0.005s

Вы также можете просто запросить один тип времени, например, utime:

[email protected] $ utime="$( TIMEFORMAT='%lU';time ( ls ) 2>&1 1>/dev/null )"
[email protected] $ echo "$utime"
0m0.000s

Чтобы получить время, вы также можете использовать date +%s.%N, поэтому возьмите его до и после выполнения и рассчитайте разницу:

START=$(date +%s.%N)
command
END=$(date +%s.%N)
DIFF=$(echo "$END - $START" | bc)
# echo $DIFF
89
binfalse

В bash выходные данные конструкции time возвращаются к стандартной ошибке, и вы можете перенаправить стандартную ошибку конвейера, на которую она влияет. Итак, давайте начнем с команды, которая записывает в свой вывод и сообщения об ошибках: sh -c 'echo out; echo 1>&2 err'. Чтобы не перепутать поток ошибок команды с выводом из time, мы можем временно перенаправить поток ошибок команды в другой файловый дескриптор:

{ time -p sh -c 'echo out; echo 1>&2 err' 2>&3; }

Это записывает out в fd 1, err в fd 3 и время в fd 2:

{ time -p sh -c 'echo out; echo 1>&2 err' 2>&3; } \
    3> >(sed 's/^/ERR:/') 2> >(sed 's/^/TIME:/') > >(sed 's/^/OUT:/')

Было бы более приятно иметь err на fd 2 и время на fd 3, поэтому мы поменяем их местами, что обременительно, потому что нет прямого способа поменять местами два дескриптора файла:

{ { { time -p sh -c 'echo out; echo 1>&2 err' 2>&3; } 3>&2 2>&4; } 4>&3; } 3> >(sed 's/^/TIME:/') 2> >(sed 's/^/ERR:/') > >(sed 's/^/OUT:/')

Это показывает, как вы можете постобработать вывод команды, но если вы хотите захватить как вывод команды, так и ее время, вам нужно работать усерднее. Использование временного файла является одним из решений. Фактически, это единственное надежное решение, если вам нужно зафиксировать как стандартную ошибку команды, так и ее стандартный вывод. Но в противном случае вы можете захватить весь вывод и воспользоваться тем, что time имеет предсказуемый формат (если вы используете time -p чтобы получить формат POSIX или переменную TIMEFORMAT для bash).

nl=$'\n'
output=$(TIMEFORMAT='%R %U %S %P'; mycommand)
set ${output##*$nl}; real_time=$1 user_time=$2 system_time=$3 cpu_percent=$4
output=${output%$nl*}

Если вы заботитесь только о времени настенных часов, выполнение date до и после является простым решением (если немного более неточным из-за дополнительного времени, потраченного на загрузку внешней команды).

17
Gilles 'SO- stop being evil'

Если вы находитесь в bash (а не в sh) и вам НЕ нужна точность менее секунды, вы можете полностью пропустить вызов date и ​​сделать это, не вызывая дополнительных процессы, без необходимости отделять объединенный вывод, и без необходимости собирать и анализировать вывод из каких-либо команд:

# SECONDS is a bash special variable that returns the seconds since set.
SECONDS=0
mycmd <infile >outfile 2>errfile
DURATION_IN_SECONDS=$SECONDS
# Now `$DURATION_IN_SECONDS` is the number of seconds.
8
sanpath

Со временем выходные данные команды выводятся на стандартный вывод, а время - на стандартный вывод. Итак, чтобы разделить их, вы можете сделать:

command time -f '%e' [command] 1>[command output file] 2>[time output file]

Но сейчас время в файле. Я не думаю, что Bash может напрямую помещать stderr в переменную. Если вы не против перенаправить вывод команды куда-то, вы можете сделать:

FOO=$((( command time -f '%e' [command]; ) 1>outputfile; ) 2>&1; )

Когда вы сделаете это, выходные данные команды будут в outputfile, а время, необходимое для выполнения, будет в $FOO.

7
marinus

Во все предыдущие ответы, когда в OSX

ProductName:    Mac OS X
ProductVersion: 10.11.6
BuildVersion:   15G31+

ты можешь делать как

microtime() {
    python -c 'import time; print time.time()'
}
compute() {
    local START=$(microtime)
    #$1 is command $2 are args
    local END=$(microtime)
    DIFF=$(echo "$END - $START" | bc)
    echo "$1\t$2\t$DIFF"
}
4
loretoparisi

Для этой цели, вероятно, лучше использовать times (чем time) в bash, который печатает накопленное пользовательское и системное время для командной консоли и для процессов, запускаемых из командной консоли, например использовать:

$ (sleep 2; times) | (read tuser tsys; echo $tuser:$tsys)
0m0.001s:0m0.003s

Видеть: help -m times для получения дополнительной информации.

4
kenorb

Установить /bin/time (например, pacman -S time)

Так что вместо ошибки при попытке -f флаг:

$ time -f %e sleep 0.5
bash: -f: command not found

real    0m0.001s
user    0m0.001s
sys     0m0.001s

Вы можете фактически использовать это:

$ /bin/time -f %e sleep 0.5
0.50

И получите то, что вы хотите - время в переменной (пример использует %e для реального истекшего времени, для других параметров проверьте man time):

#!/bin/bash
tmpf="$(mktemp)"
/bin/time -f %e -o "$tmpf" sleep 0.5
variable="$(cat "$tmpf")"
rm "$tmpf"

echo "$variable"
1
Grzegorz Wierzowiecki

Так же, как хедз-ап, работая с приведенными выше утверждениями и особенно принимая во внимание ответ Гжегожа. Я был удивлен, увидев на моей системе Ubuntu 16.04 Xenial эти два результата:

$ which time
/usr/bin/time

$ time -f %e sleep 4
-f: command not found
real    0m0.071s
user    0m0.056s
sys     0m0.012s

$ /usr/bin/time -f %e sleep 4
4.00

У меня не установлены псевдонимы, и поэтому я не знаю, почему это происходит.

1
Paul Pritchard

попробуйте это, он запускает простую команду с аргументами и помещает времена $ real $ user $ sys и сохраняет код выхода.

Он также не разветвляет подоболочки и не вмешивается ни в какие переменные, кроме реального пользователя sys, и не мешает выполнению сценария.

timer () {
  { time { "[email protected]" ; } 2>${_} {_}>&- ; } {_}>&2 2>"/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  set -- $?
  read -d "" _ real _ user _ sys _ < "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  rm -f "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  return $1
}

например.

  timer find /bin /sbin /usr rm /tmp/
  echo $real $user $sys

примечание: это всего лишь простая команда, а не конвейер, компоненты которого запускаются в подоболочке

Эта версия позволяет указать в качестве $ 1 имя переменных, которые должны получить 3 раза:

timer () {
  { time { "${@:4}" ; } 2>${_} {_}>&- ; } {_}>&2 2>"/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  set -- $? "[email protected]"
  read -d "" _ "$2" _ "$3" _ "$4" _ < "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  rm -f "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  return $1
}

например.

  timer r u s find /bin /sbin /usr rm /tmp/
  echo $r $u $s

и может быть полезно, если в конечном итоге он вызывается рекурсивно, чтобы не растоптать время; но тогда они должны быть объявлены локальными при их использовании.

http://blog.sam.liddicott.com/2016/01/timeing-bash-commands.html

0
Sam Liddicott