Як вимірювати час виконання програми та зберігати її всередині змінної


61

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

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

Мені хочеться - це функціональність підрахунку кількості часу в секундах (цілих числа) між запуском команди та її завершенням. Це не повинно бути timeкомандою або відповідним вбудованим.


Редагувати: з огляду на дві корисні відповіді нижче, я хотів додати два пояснення.

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

Наразі рішення, яке використовується, dateзакриває те, що я хочу.


Найбільш прямим способом отримати дані та обробити їх, зберігаючи нормальний запуск, - це зробити це в програмі C, використовуючи fork(), execvp()і wait3()/wait4(). Це в кінцевому підсумку те, що роблять час та друзі. Мені невідомий простий спосіб зробити це в bash / perl без перенаправлення на файл або подібний підхід.
penguin359

Тут є пов’язане питання, яке може бути цікавим для вас .
Калеб

@Caleb: спасибі Дійсно цікаво. Однак для моїх цілей достатньо зробити це за сценарієм.
0xC0000022L

Відповіді:


85

Щоб отримати вихід timeу var, використовуйте наступне:

usr@srv $ mytime="$(time ( ls ) 2>&1 1>/dev/null )"
usr@srv $ echo "$mytime"

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

Ви також можете просто запитати для одного типу часу, наприклад, utime:

usr@srv $ utime="$( TIMEFORMAT='%lU';time ( ls ) 2>&1 1>/dev/null )"
usr@srv $ echo "$utime"
0m0.000s

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

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

4
Я не хотів викидати вихід з команди. Тож я думаю, що ваш третій код коду найближчий до того, що я мав на увазі. Хоча я би написав останній як DIFF=$((END-START)), використовуючи арифметичні вирази. :) ... дякую за відповідь. +1
0xC0000022L

1
@STATUS_ACCESS_DENIED: Bash не виконує арифметику з плаваючою комою, тому якщо ви хочете краще, ніж друга роздільна здатність ( .%Nу коді binfalse), вам потрібні bcабо більш химерні розрахунки.
Жиль,

@Gilles: Я знаю. Як я писав у своєму питанні цілі числа добре. Не потрібно мати більш високу роздільну здатність, ніж секунду. Але дякую, виклик дати потрібно буде змінити. Я зрозумів, що це.
0xC0000022L

6
FYI, форматування дати% N, здається, не працює на Mac OS X, вона просто повертає "N". Гаразд на Ubuntu.
Девід

Зауважте, що хоча time (cmd) 2> somethingпрацює над перенаправленням часу виведення file, це не передбачається (відповідно до документації), але не в інших оболонках, де timeє ключовим словом, і може розглядатися як помилка . Я б не покладався на нього, оскільки це може не працювати в майбутніх версіях bash.
Стефан Хазелас

17

В основному, вихід timeконструкції переходить до його стандартної помилки, і ви можете перенаправити стандартну помилку трубопроводу, на яку він впливає. Отже , давайте почнемо з командою , яка записує його виведення і помилок streamas: 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до і після - це просте рішення (якщо дещо більш неточний через додатковий час, витрачений на завантаження зовнішньої команди).


Нічого собі, тестувати багато. Дякую велику за широку відповідь. +1.
0xC0000022L

7

З часом командний вихід виходить на stdout, а час виходить на stderr. Отже, щоб розділити їх, ви можете зробити:

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.


1
ой, я не усвідомлював. Я знав, що timeпише в stderr, але не розумів, що це об'єднає stdout і stderr виконаної команди в її stdout. Але взагалі прямого методу немає (тобто без проміжного файлу)? +1.
0xC0000022L

3
@STATUS_ACCESS_DENIED: timeне зливає свої команди stdoutта stderr. Те, як я показав, припускає, що вам потрібен лише вихід команди stdout. Оскільки bashбуде зберігатися лише те, що виходить stdout, вам доведеться перенаправляти. Якщо ви можете сміливо скинути stderr команди: час завжди буде в останньому рядку stderr. Якщо вам потрібні обидва вихідні потоки вашої команди, я б запропонував упакувати її в інший сценарій.
marinus

6

Якщо ви перебуваєте в 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.

Приємно. Я ніколи раніше не чув про SECONDS
Bruno9779

Чи трапляється вам знати, починаючи з якої версії Bash це було представлено?
0xC0000022L

4

З цією метою, мабуть, краще використовувати times(ніж time), в bashякому друкує накопичені користувацькі та системні часи для оболонки та для процесів, що запускаються з оболонки, наприклад, використовуйте:

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

Дивіться: help -m timesдля отримання додаткової інформації.


4

У складі всіх разом попередні відповіді, коли в 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"
}

1

Встановити /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"

/usr/bin/timeна багатьох системах
Базиль Старинкевич,

Якщо ви придивитесь уважніше, я це вказав у своєму питанні. timeце оболонка, вбудована в Bash (і, імовірно, в інші оболонки), але поки існує шлях до timeвиконуваного файлу PATH, ми можемо використовувати, command timeщоб переконатися, що ми виконуємо зовнішню команду на відміну від вбудованої.
0xC0000022L

1

Подібно до того, як працювати з вищезазначеними твердженнями і особливо враховуючи відповідь Гжегожа. Я був здивований, побачивши у своїй системі 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
timeтакож є вбудованою оболонкою.
Базиль Старинкевич

Якщо ви хочете переконатися, що ви виконуєте команду, використовуйте command, якщо ви хочете переконатися, що ви працюєте з вбудованим, використовуйте builtin. Тут немає ніякої магії. Використовуйте helpдля з'ясування наявних команд або type <command>для визначення типу <command>(наприклад, вбудована, зовнішня команда, функція чи псевдонім).
0xC0000022L

Базиле, ти абсолютно прав. Я не помітив значення commandкоманди. Що є фактом, що забезпечує / usr / bin / time. Це означає, що наступні два твердження сумісні: $ command time -f %e sleep 4і$ /usr/bin/time -f %e sleep
Пол Притчард

0

спробуйте це, він запускає просту команду з аргументами і ставить рази $ real $ user $ sys і зберігає вихідний код.

Він також не розщеплює підрозділи та не топтує будь-які змінні, окрім реальних системних користувачів, і не впливає на виконання сценарію.

timer () {
  { time { "$@" ; } 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 -- $? "$@"
  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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.