it-swarm.it

Come misurare il tempo di esecuzione del programma e memorizzarlo all'interno di una variabile

Per scoprire quanto tempo impiegano determinate operazioni all'interno di uno script Bash (v4 +), vorrei analizzare l'output del comando time "separatamente" e (in definitiva) catturarlo all'interno di una variabile Bash (let VARNAME=...).

Ora sto usando time -f '%e' ... (o piuttosto command time -f '%e' ... a causa del built-in di Bash), ma poiché ho già reindirizzato l'output del comando eseguito, sono davvero perso su come farei per catturare l'output del comando time. Fondamentalmente il problema qui è per separare l'output di time dall'output dei comandi eseguiti.

Quello che voglio è funzionalità di contare la quantità di tempo in secondi (numeri interi) tra l'avvio di un comando e il suo completamento. Non deve essere il comando time o il rispettivo built-in.


Modifica: date le due utili risposte di seguito, volevo aggiungere due chiarimenti.

  1. Non voglio buttare via l'output del comando eseguito, ma non importa se finisce su stdout o stderr.
  2. Preferirei un approccio diretto rispetto a uno indiretto (cioè catturare l'output direttamente anziché memorizzarlo in file intermedi).

La soluzione che utilizza date finora viene chiusa a ciò che voglio.

65
0xC0000022L

Per ottenere l'output di time in una var, utilizzare quanto segue:

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

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

Puoi anche chiedere un solo tipo di orario, ad es. utime:

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

Per ottenere il tempo puoi anche usare date +%s.%N, quindi prendilo prima e dopo l'esecuzione e calcola il diff:

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

In bash, l'output del costrutto time va al suo errore standard e puoi reindirizzare l'errore standard della pipeline che influenza. Quindi iniziamo con un comando che scrive nei suoi output e streamas di errore: sh -c 'echo out; echo 1>&2 err'. Per non confondere il flusso di errori del comando con l'output di time, è possibile deviare temporaneamente il flusso di errori del comando in un descrittore di file diverso:

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

Questo scrive out in fd 1, err in fd 3 e i tempi in 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:/')

Sarebbe più piacevole avere err su fd 2 e i tempi su fd 3, quindi li scambiamo, il che è scomodo perché non esiste un modo diretto per scambiare due descrittori di file:

{ { { 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:/')

Questo mostra come è possibile postelaborare l'output del comando, ma se si desidera acquisire sia l'output del comando sia i relativi tempi, è necessario lavorare di più. L'uso di un file temporaneo è una soluzione. In effetti, è l'unica soluzione affidabile se è necessario acquisire sia l'errore standard del comando sia l'output standard. Altrimenti, puoi catturare l'intero output e trarre vantaggio dal fatto che time ha un formato prevedibile (se usi time -p per ottenere formato POSIX o la variabile TIMEFORMAT specifica per 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*}

Se ti interessa solo il tempo dell'orologio da parete, eseguire date prima e dopo è una soluzione semplice (se leggermente più imprecisa a causa del tempo extra impiegato per caricare il comando esterno).

Se sei in bash (e non sh) e NON hai bisogno di precisione al secondo, puoi saltare la chiamata a date completamente e farlo senza generare alcun extra elabora, senza dover separare l'output combinato e senza dover acquisire e analizzare l'output da qualsiasi comando:

# 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

Con il tempo, l'output del comando esce su stdout e il tempo esce su stderr. Quindi, per separarli, puoi fare:

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

Ma ora è il momento in un file. Non penso che Bash sia in grado di inserire direttamente stderr in una variabile. Se non ti dispiace reindirizzare l'output del comando da qualche parte, puoi fare:

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

Quando lo fai, l'output del comando sarà in outputfile e il tempo impiegato per l'esecuzione sarà in $FOO.

7
marinus

Nel mettere insieme tutte le risposte precedenti, quando in OSX

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

puoi fare come

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

A tale scopo è probabilmente meglio usare times (rispetto a time) in bash che stampa i tempi accumulati di utente e sistema per Shell e per i processi eseguiti da Shell, ad esempio uso:

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

Vedi: help -m times per maggiori informazioni.

4
kenorb

Installa /bin/time (Ad es. pacman -S time)

Quindi invece di errore quando si prova il flag -f:

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

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

Puoi effettivamente usarlo:

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

E ottieni quello che vuoi - tempo in variabile (esempio usa %e Per il tempo reale trascorso, per altre opzioni controlla man time):

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

echo "$variable"
1

Proprio come un avvertimento quando si lavora con le dichiarazioni di cui sopra e soprattutto tenendo presente la risposta di Grzegorz. Sono stato sorpreso di vedere sul mio sistema Ubuntu 16.04 Xenial questi due risultati:

$ 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

Non ho nessun alias impostato e quindi non so perché questo accada.

1
Paul Pritchard

prova questo, esegue un semplice comando con argomenti e inserisce i tempi $ real $ user $ sys e conserva il codice di uscita.

Inoltre, non esegue il fork delle subshells o calpesta le variabili tranne il vero sistema utente e non interferisce altrimenti con l'esecuzione dello script

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
}

per esempio.

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

nota: per il comando semplice non è solo una pipeline, i cui componenti vengono eseguiti in una subshell

Questa versione consente di specificare come $ 1 il nome delle variabili che dovrebbero ricevere le 3 volte:

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
}

per esempio.

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

e può essere utile se finisce per essere chiamato ricorsivamente, per evitare di calpestare i tempi; ma allora eccetera dovrebbe essere dichiarato locale nel loro uso.

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

0
Sam Liddicott