Hur får man exekveringstid för ett skript effektivt?

Jag vill visa slutdatum för ett skript.

Vad jag för närvarande gör är –

#!/bin/bash date ## echo the date at start # the script contents date ## echo the date at end 

Detta visar bara tidpunkten för manusens början och slut. Skulle det vara möjligt att visa en finkornig utgång som processortid / io-tid, etc?

Kommentarer

Svar

Jus använd time när du ringer till skriptet:

time yourscript.sh 

Kommentarer

  • Detta matas ut tre gånger: real, user och sys. För betydelserna av dessa, se här .
  • och ” real ” är förmodligen vad folk vill veta – ” Real är tid för väggklocka – tid från början till slut för samtalet ”
  • Finns det ett sätt att fånga in stdout i en fil? Till exempel något som time $( ... ) >> out.txt
  • Du kan också göra detta för sekvenser av kommandon på kommandoraden, t.ex.: time (command1 && command2)
  • Eller så kan han bara göra i sitt manus: echo $ SECONDS förutsatt att det är bash ….

Svar

Om time inte är ett alternativ,

start=`date +%s` stuff end=`date +%s` runtime=$((end-start)) 

Kommentarer

  • Observera att det här bara fungerar om du inte behöver ’ t behöver precision under andra sekunden. För vissa användningar kan det vara acceptabelt , för andra inte. För lite bättre precision (du ’ åberopar fortfarande date två gånger, till exempel, så kan du till bästa få millisekundprecision i praktiken, och förmodligen mindre), försök att använda date +%s.%N. (%N är nanosekunder sedan hela den andra .)
  • @ChrisH Åh. Bra att påpeka det; bash-aritmetisk expansion är endast heltal. Jag ser två obvi andra alternativ; antingen dike perioden i datumformatsträngen (och behandla det resulterande värdet som nanosekunder sedan epoken), så använd date +%s%N, eller använd något mer kapabelt som bc för att beräkna den faktiska körtiden från de två värdena som jwchew föreslår. Ändå tycker jag att detta tillvägagångssätt är ett suboptimalt sätt att göra det; time är betydligt bättre om det är tillgängligt, av skäl som beskrivs ovan.
  • Installera bara bc och gör sedan detta: runtime=$( echo "$end - $start" | bc -l )
  • Om ditt skript tar flera minuter, använd: echo "Duration: $((($(date +%s)-$start)/60)) minutes
  • Vidare till @ rubo77 kommentera, om ditt skript tar flera timmar, använd hours=$((runtime / 3600)); minutes=$(( (runtime % 3600) / 60 )); seconds=$(( (runtime % 3600) % 60 )); echo "Runtime: $hours:$minutes:$seconds (hh:mm:ss)"

Svar

Ring bara times utan argument när du avslutar skriptet.

Med ksh eller zsh, du kan också använda time istället. Med zsh, time ger dig också tid för väggklockan förutom användaren och system CPU-tid.

För att bevara utgångsstatus för ditt skript kan du göra det:

ret=$?; times; exit "$ret" 

Eller du kan också lägga till en fälla på EXIT:

trap times EXIT 

På det sättet kommer tider att kallas när skalet går ut och utgångsstatusen bevaras.

$ 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 

Observera också att alla bash, ksh och zsh har en $SECONDS specialvariabel som automatiskt ökas varje sekund. I både zsh och ksh93 kan variabeln också göras flytande (med typeset -F SECONDS ) för att få mer precision. Detta är bara tid för väggklocka, inte CPU-tid.

Kommentarer

  • Den variabeln $ SECONDS är väldigt användbar, tack!
  • Eliminerar din inställning effekten av tidsmässiga händelser? – – Jag tror att ditt tillvägagångssätt är nära time tillvägagångssätt som presenterades tidigare.- – Jag tror att timeit -metoden som presenteras i MATLAB-koden kan vara användbar här.
  • Hej, @St é fasan Chazelas. Jag hittade att times ger ’ inte väggtid, eller hur? till exempel bash -c 'trap times EXIT;sleep 2'
  • @Binarus, ja, den funktionen kommer från ksh. I motsats till ksh93 / zsh, bredvid bash som inte stöder flytpunkt, är det ’ också brutet genom att när du har ställt SECONDS till 0, kommer det att ändras till 1 från 0 till 1 sekund senare ( eftersom det bara tar hänsyn till resultatet av time() som bara har full andra granularitet).
  • @Binarus, inte att ’ s mellan 0 och 1. Om du ställer in SECONDS till 0 vid 12: 00: 00.000 ändras det till 1 en sekund senare. Men om du ställer in klockan 12.00: 00.999 ändras det till en milisekund senare. Men ja, jag håller med om att det troligtvis inte ’ betyder något i praktiken om du inte ’ inte har någon sekunders precision ändå.

Svar

Jag var lite sen till vagnen, men ville lägga upp min lösning (för precision under sekunden ) om andra råkar stöta på den här tråden genom sökning. Utdata är i format av dagar, timmar, minuter och slutligen sekunder:

res1=$(date +%s.%N) # do stuff in here 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 "Total runtime: %d:%02d:%02d:%02.4f\n" $dd $dh $dm $ds 

Hoppas någon där ute tycker det är användbart!

Kommentarer

  • Jag antar att det inte finns något annat (endast bash) sätt än att använda ’ bc ’ för att göra beräkningarna. BTW riktigt bra skript;)
  • Var uppmärksam på att FreeBSD ’ s date stöder inte sekunders precision och kommer bara att lägga till bokstavligt “N” till tidsstämpeln.
  • Mycket trevligt skript, men min bash ’ t hantera den andra delen. Jag lärde mig också att / 1 i bc effektivt tar bort det, så jag lade till @ / 1 @ i $ ds-beräkningen, och det visar saker mycket trevligt nu!
  • bc stöder modulo: dt2=$(echo "$dt%86400" | bc). Jag säger bara … BTW Jag föredrar formuläret dt2=$(bc <<< "${dt}%86400") men att ’ är helt personligt.
  • Jag don ’ vet inte någonting om bc än, men delningen av 86400 gör mig misstro mot följande anledning: Det finns dagar som inte har 86400 sekunder, till exempel dagar där tiden byts från sommartid till normal tid och vice versa, eller dagar med språngsekunder. Om sådana dagar är en del av det tidsintervall du vill beräkna, dvs. ligger mellan res1 och res2 (inklusive dessa värden), ditt skript kommer troligen att misslyckas.

Svar

Min metod för bash :

# Reset BASH time counter SECONDS=0 # # do stuff # ELAPSED="Elapsed: $(($SECONDS / 3600))hrs $((($SECONDS / 60) % 60))min $(($SECONDS % 60))sec" 

Kommentarer

  • Detta visar förfluten tid, men ’ t visar ” finkornig utgång som processortid, I / O-tid ” som begärts i frågan.
  • De som letar efter ett svar på den ställda frågan kommer sannolikt att hitta den här lösningen användbar. Din kommentar skulle vara bättre adresserad till OPs-frågan.

Svar

Personligen gillar jag alla mina skriptkod i någon ”huvud” -funktion som så:

main () { echo running ... } # stuff ... # calling the function at the very end of the script time main 

Lägg märke till hur enkelt det är att använda kommandot time i detta scenario. Uppenbarligen mäter du inte den exakta tiden inklusive tid för skriptanalys, men jag tycker att det är tillräckligt exakt i de flesta situationer.

Kommentarer

  • Bra lösning, kräver ’ inte extra verktyg och är fristående.

Svar

#!/bin/bash start=$(date +%s.%N) # HERE BE CODE end=$(date +%s.%N) runtime=$(python -c "print(${end} - ${start})") echo "Runtime was $runtime" 

Ja, det här kallar Python, men om du kan leva med det är det här en ganska trevlig, kortfattad lösning.

Kommentarer

  • Var uppmärksam på att det att ta det python -kommandot i en subshell och läsa utdata tar flera miljoner nanosekunder på de flesta nuvarande system. div id = ”33e4a5674d”>

.

  • ” Flera miljoner nanosekunder ” är flera millisekunder Människor som timar bash-skript är vanligtvis inte särskilt bekymrade över det (såvida de inte kör flera miljarder skript per megasekund).
  • Lägg också märke till att även om beräkningen görs i en pythonprocess, värdena samlades helt innan python åberopades. Körningstiden för skriptet mäts korrekt utan ” miljoner nanosekunder ” overhead.
  • time main ovan är så mycket renare …ja, vi kan leva med mycket nedbrytning men det ’ är bättre utan så låt ’ vara ingenjörer!
  • $(echo $end - $start | bc) kommer att göra samma sak utan python
  • Svar

    Den här frågan är ganska gammal men när jag försökte hitta mitt favorit sätt att göra det kom den här tråden upp högt … och jag förvånade mig att ingen nämnde den:

    perf stat -r 10 -B sleep 1 

    ”perf” är ett prestandaanalyseringsverktyg som ingår i kärnan under ”tools / perf” och ofta tillgängligt för installation som ett separat paket (”perf” i CentOS och ”linux-tools” på Debian / Ubuntu). Linux Kernal perf Wiki har mycket mer information om det.

    Att köra ”perf stat” ger en hel del detaljer inklusive genomsnittlig körtid precis i slutet:

    1.002248382 seconds time elapsed ( +- 0.01% ) 

    Kommentarer

    • Vad är perf?
    • @B Layer – redigerat mitt svar t o ge en kort beskrivning av ’ perf ’.
    • perf stat klagar nu på ” Du kanske inte har behörighet att samla in statistik. ” om du inte kör några kommandon med sudo, vilket gör det värdelöst i alla scenarier där du inte ’ t helt egen målmaskin.
    • Fantastiskt, tack! Sätt mer finkornig lösning än time andra föreslår. I synnerhet är time inte tillämplig på mätning av kodkonkurrenslösningar (eftersom den inte ’ t skriver ut i millisekunder och mindre) medan din lösning gör det.

    Svar

    En liten skalfunktion som kan läggas till innan kommandon för att mäta sin tid:

    tm() { local start=$(date +%s) $@ local exit_code=$? echo >&2 "took ~$(($(date +%s)-${start})) seconds. exited with ${exit_code}" return $exit_code } 

    Använd den sedan i ditt skript eller på din kommandorad så:

    tm the_original_command with all its parameters 

    Kommentarer

    • Det vore värt att lägga till millisekunder, försökte ilke s=$(date +%s000), inte säker om det fungerar.
    • @loretoparisi under milsekunder kan du prova date +%s%N. Se serverfault.com/questions/151109/…
    • Det här är bra, utom säkrare använd "$@"

    Svar

    #!/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 "It takes DIFF=$DIFF seconds to complete this task..." 

    Kommentarer

    • Hur skiljer sig detta egentligen från de andra svar som redan ges som använder date före och efter skriptet och mata ut skillnaden mellan dem?

    Svar

    1. Bara använd time [any command] . Ex: time sleep 1 kommer att sova i realtid (dvs. enligt en stoppur) på ~ 1.000 till ~ 1.020 sek, som visas här:

        $ time sleep 1 real 0m1.011s user 0m0.004s sys 0m0.000s  

      Vilken vacker sak. Du kan lägga valfritt kommando efter det och resultatet ger resultatet i en trevlig, läsbar form. Jag gillar verkligen att använda den för att bygga timing. Ex:

        # time your "make" build time make # time your "Bazel" build time bazel build //path/to/some:target  

      … eller för git-operationer som potentiellt kan vara riktigt lång så att jag kan utveckla realistiska mentala förväntningar:

        # time how long it takes to pull from a massive repo when # I"m working from home during COVID-19. NB: `git pull` # is sooooo much slower than just pulling the one branch # you need with `git pull origin <branch>`, so just fetch # or pull what you need! time git pull origin master  
    2. För mer anpassade tidsbehov där du kan behöva manipulera utdata eller konvertera den till andra former, i bash , använd den interna variabeln $SECONDS. Här ”är en demo, inklusive konvertering av dessa sekunder till andra enheter, till exempel flytande punktminuter:

      Observera att dt_min avrundas från 0.01666666666... (1 sekund = så många minuter) till 0.017 i det här fallet eftersom jag använder printf -funktionen för att runda. sleep 1; -delen nedan är där du skulle kalla ditt skript för att köra och tid, men jag sover bara i en sekund istället för denna demo.

      Kommando:

        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"  

      Output:

        dt_sec = 1; dt_min = 0.017  

    Relaterat:

    1. Läs mer om bc och printf i mitt svar här: https://stackoverflow.com/questions/12722095/how-do-i-use-floating-point-division-in-bash/58479867#58479867
    2. Jag kommer inte ihåg var jag först lärde mig om kommandot time, men det kan ha varit från @Trudbert svar direkt här .

    Svar

    Använd endast bash är det också möjligt att mäta och beräkna tidslängden för en del av skalskriptet (eller den förflutna tiden för hela skriptet):

    start=$SECONDS ... # do time consuming stuff end=$SECONDS 

    du kan nu bara skriva ut skillnaden:

    echo "duration: $((end-start)) seconds." 

    om du bara behöver inkrementell varaktighet gör:

    echo "duration: $((SECONDS-start)) seconds elapsed.." 

    Du kan också lagra varaktigheten i en variabel:

    let diff=end-start 

    Kommentarer

    • ^ DETTA är svaret folk. Eller enklare: $ SECONDS i slutet. Det ’ är en INBYGGD Bash-variabel. Alla andra svar gör bara extra arbete för att uppfinna detta hjul igen …
    • Detta är bara en upprepning av Marks tidigare svar unix.stackexchange .com / a / 340156/155362 .

    Svar

    Timing-funktion baserad på SECONDS, begränsad till enbart granularitet på andra nivå, använder inga externa kommandon:

    time_it() { local start=$SECONDS ts ec printf -v ts "%(%Y-%m-%d_%H:%M:%S)T" -1 printf "%s\n" "$ts Starting $*" "$@"; ec=$? printf -v ts "%(%Y-%m-%d_%H:%M:%S)T" -1 printf "%s\n" "$ts Finished $*; elapsed = $((SECONDS-start)) seconds" # make sure to return the exit code of the command so that the caller can use it return "$ec" } 

    Till exempel:

    time_it sleep 5 

    ger

    2019-03-30_17:24:37 Starting sleep 5 2019-03-30_17:24:42 Finished sleep 5; elapsed = 5 seconds 

    Svar

    Använd bash-tid inbyggd?

    time: time [-p] PIPELINE Execute PIPELINE and print a summary of the real time, user CPU time, and system CPU time spent executing PIPELINE when it terminates. The return status is the return status of PIPELINE. The `-p" option prints the timing summary in a slightly different format. This uses the value of the TIMEFORMAT variable as the output format. 

    Exempel:

    TIMEFORMAT="The command took %Rs" time { sleep 0.1 } 

    Output:

    The command took 0.108s 

    Svar

    Här ”är en variant av Alex ”Svar. Jag bryr mig bara om minuter och sekunder, men jag ville också att det skulle formateras annorlunda. Så jag gjorde det här:

    start=$(date +%s) end=$(date +%s) runtime=$(python -c "print "%u:%02u" % ((${end} - ${start})/60, (${end} - ${start})%60)") 

    Kommentarer

    • Varför nedröstningen? Har jag ett fel?
    • Att använda python för den beräkningen ser ut som ett fel för mig heller, sorry man. Varför människor kan ’ inte bara ringa time är bortom mig.

    Svar

    #!/bin/bash begin=$(date +"%s") Script termin=$(date +"%s") difftimelps=$(($termin-$begin)) echo "$(($difftimelps / 60)) minutes and $(($difftimelps % 60)) seconds elapsed for Script Execution." 

    Svar

    Den accepterade lösningen använder time skriver till stderr.
    Lösningen med times skriver till stdout.
    Lösningarna som använder $SECONDS saknar precision under sekunden.
    De andra lösningarna innefattar att ringa externa program som date eller perf vilket inte är effektivt.
    Om du mår bra med något av dessa, använd det.

    Men om du behöver en effektiv lösning för att få tiden med millisekund precision och behöver dem till variabler så att originalutdata förblir ostörd du kan kombinera processersättning med några omdirigeringar runt time vilket är mycket snabbare än att ringa externa program och möjliggör omdirigeringar kring timingomslagsskriptet som på det ursprungliga kommandot / skriptet.

    # Preparations: Cmd=vgs # example of a program to be timed which is writing to stdout and stderr Cmd="eval { echo stdout; echo stderr >&2; sleep 0.1; }" # other example; replace with your own TIMEFORMAT="%3R %3U %3S" # make time output easy to parse 

    Välj en av följande varianter som analyserar utdata från time som passar dig:
    Kortaste variant där stdout av $Cmd skrivs till stderr och inget till stdout:

    read Elapsed User System < <({ time $Cmd 2>&3; } 3>&2 2>&1 >&3) 

    Längre variant som håller original stdout och stderr åtskilda från varandra:

    { read Elapsed User System < <({ time $Cmd 2>&4; } 4>&2 2>&1 >&3); } 3>&1 

    Den mest komplicerade varianten inkluderar att stänga de extra filbeskrivarna så att $Cmd kallas som om utan denna timing-omslag runt den och lvm kommandon som vgs klagar inte på läckta filbeskrivare:

    { read Elapsed User System < <({ time $Cmd 2>&4 4>&-; } 4>&2 2>&1 >&3 3>&-); } 3>&1 

    Du kan till och med förfalska ett flytande tillägg i bash utan att ringa bc vilket skulle vara mycket långsammare:

    CPU=`printf %04d $((10#${User/.}+10#${System/.}))` # replace with your own postprocessing echo CPU ${CPU::-3}.${CPU: -3} s, Elapsed $Elapsed s >&2 # redirected independent of $Cmd 

    Möjliga utgångar med de två exemplen på $Cmd på en långsam CPU:

    File descriptor 3 (/dev/pts/1) leaked on vgs invocation. Parent PID 10756: bash File descriptor 4 (/dev/pts/1) leaked on vgs invocation. Parent 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 

    Eller:

    stdout stderr CPU 0.008 s, Elapsed 0.109 s 

    Lämna ett svar

    Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *