Bagaimana mengukur waktu pelaksanaan program dan menyimpannya di dalam variabel

61

Untuk mengetahui berapa lama operasi tertentu dalam skrip Bash (v4 +), saya ingin mengurai output dari timeperintah "secara terpisah" dan (akhirnya) menangkapnya dalam variabel Bash ( let VARNAME=...).

Sekarang, saya menggunakan time -f '%e' ...(atau lebih tepatnya command time -f '%e' ...karena Bash built-in), tetapi karena saya sudah mengarahkan output dari perintah yang dieksekusi, saya benar-benar bingung bagaimana saya akan menangkap output dari timeperintah. Pada dasarnya masalah di sini adalah untuk memisahkan output dari timeoutput dari perintah yang dieksekusi.

Yang saya inginkan adalah fungsionalitas menghitung jumlah waktu dalam detik (bilangan bulat) antara memulai perintah dan penyelesaiannya. Itu tidak harus menjadi timeperintah atau masing-masing built-in.


Sunting: mengingat dua jawaban yang berguna di bawah ini, saya ingin menambahkan dua klarifikasi.

  1. Saya tidak ingin membuang output dari perintah yang dieksekusi, tetapi tidak masalah apakah itu berakhir pada stdout atau stderr.
  2. Saya lebih suka pendekatan langsung daripada yang tidak langsung (yaitu menangkap output secara langsung sebagai lawan menyimpannya dalam file perantara).

Solusi menggunakan datesejauh ini mendekati apa yang saya inginkan.

0xC0000022L
sumber
Cara paling langsung untuk mendapatkan data dan menanganinya sambil tetap membiarkannya berjalan normal adalah dengan melakukannya dalam program C menggunakan fork(), execvp()dan wait3()/wait4(). Inilah yang akhirnya dilakukan oleh waktu dan teman-teman. Saya tidak mengetahui cara simle untuk melakukannya di bash / perl tanpa mengarahkan ke file atau pendekatan serupa.
penguin359
Ada pertanyaan terkait yang mungkin menarik bagi Anda di sini .
Caleb
@ Caleb: terima kasih. Menarik sekali. Namun, untuk keperluan saya itu sudah cukup untuk melakukannya dalam skrip.
0xC0000022L

Jawaban:

85

Untuk mendapatkan output dari timevar gunakan yang berikut ini:

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

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

Anda juga dapat meminta satu jenis waktu saja, mis. Utime:

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

Untuk mendapatkan waktu yang juga bisa Anda gunakan date +%s.%N, jadi ambil sebelum dan sesudah eksekusi dan hitung perbedaannya:

START=$(date +%s.%N)
command
END=$(date +%s.%N)
DIFF=$(echo "$END - $START" | bc)
# echo $DIFF
binfalse
sumber
4
Saya tidak ingin membuang output dari perintah. Jadi saya kira blok kode ketiga Anda paling dekat dengan apa yang ada dalam pikiran saya. Meskipun saya akan menulis yang terakhir sebagai DIFF=$((END-START)), memanfaatkan ekspresi aritmatika. :) ... Terima kasih atas jawabannya. +1
0xC0000022L
1
@STATUS_ACCESS_DENIED: Bash tidak melakukan aritmatika floating point, jadi jika Anda ingin lebih baik daripada resolusi kedua ( .%Ndalam kode binfalse), Anda perlu bcatau perhitungan yang lebih bagus.
Gilles 'SANGAT berhenti menjadi jahat'
@Gilles: Saya tahu. Seperti yang saya tulis dalam bilangan bulat pertanyaan saya baik-baik saja. Tidak perlu memiliki resolusi lebih tinggi dari yang kedua. Tapi terima kasih, pemanggilan tanggal harus diubah. Tapi aku menyadari itu.
0xC0000022L
6
FYI, format tanggal% N tampaknya tidak berfungsi di Mac OS X, itu hanya mengembalikan "N". Oke di Ubuntu.
David
Perhatikan bahwa sementara time (cmd) 2> somethingpekerjaan mengarahkan ulang output waktu file, itu tidak dimaksudkan untuk (sesuai dokumentasi), tidak di shell lain di mana timekata kunci dan dapat dianggap sebagai bug . Saya tidak akan bergantung pada itu karena mungkin tidak berfungsi di versi masa depan bash.
Stéphane Chazelas
17

Dalam bash, output dari timekonstruk pergi ke kesalahan standar, dan Anda bisa mengarahkan kesalahan standar dari pipa yang terpengaruh. Jadi mari kita mulai dengan perintah yang menulis ke output dan error streamas nya: sh -c 'echo out; echo 1>&2 err'. Agar tidak mencampur aliran kesalahan perintah dengan output dari time, kita dapat sementara mengalihkan aliran kesalahan perintah ke deskriptor file yang berbeda:

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

Ini menulis outke fd 1, errke fd 3, dan waktu ke 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:/')

Akan lebih menyenangkan untuk memiliki errpada fd 2 dan waktu pada fd 3, jadi kami menukar mereka, yang rumit karena tidak ada cara langsung untuk menukar dua deskriptor 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:/')

Ini menunjukkan bagaimana Anda dapat memposting proses output dari perintah, tetapi jika Anda ingin menangkap output dari perintah dan waktu, Anda harus bekerja lebih keras. Menggunakan file sementara adalah salah satu solusinya. Sebenarnya, itu satu-satunya solusi yang andal jika Anda perlu menangkap kesalahan standar perintah dan output standarnya. Tetapi sebaliknya, Anda bisa menangkap seluruh output dan memanfaatkan fakta yang timememiliki format yang dapat diprediksi (jika Anda menggunakan time -puntuk mendapatkan format POSIX atau TIMEFORMATvariabel spesifik-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*}

Jika Anda hanya peduli dengan waktu jam dinding, menjalankan datesebelum dan sesudah adalah solusi sederhana (jika sedikit lebih tidak tepat karena waktu ekstra yang dihabiskan untuk memuat perintah eksternal).

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Wow, banyak yang harus diuji. Terima kasih banyak atas balasan yang luas. +1.
0xC0000022L
7

Dengan waktu, output perintah keluar pada stdout dan waktu keluar pada stderr. Jadi, untuk memisahkannya, Anda dapat melakukan:

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

Tapi, sekarang waktunya ada dalam file. Saya tidak berpikir Bash dapat menempatkan stderr dalam suatu variabel secara langsung. Jika Anda tidak keberatan mengarahkan output perintah di suatu tempat, Anda dapat melakukan:

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

Ketika Anda melakukan ini, keluaran perintah ini akan di outputfiledan waktu yang dibutuhkan untuk menjalankan akan di $FOO.

marinus
sumber
1
ooh, saya tidak sadar. Saya tahu timemenulis ke stderr, tetapi saya tidak menyadari bahwa itu akan menggabungkan stdout dan stderr dari perintah yang dieksekusi ke dalam stdout. Tetapi umumnya tidak ada metode langsung (yaitu tanpa file perantara)? +1.
0xC0000022L
3
@STATUS_ACCESS_DENIED: timetidak menggabungkan perintah stdoutdan stderr. Cara saya menunjukkan diasumsikan bahwa Anda hanya perlu keluaran stdout perintah. Karena bashhanya akan menyimpan apa yang keluar stdout, Anda harus mengarahkan ulang. Jika Anda dapat dengan aman menjatuhkan stderr perintah Anda: waktu akan selalu berada di baris terakhir stderr. Jika Anda memang membutuhkan kedua aliran output perintah Anda, saya sarankan membungkusnya dalam skrip lain.
marinus
6

Jika Anda berada di bash(dan tidak sh) dan Anda TIDAK perlu akurasi sub-detik, Anda dapat melewatkan panggilan untuk datesepenuhnya dan melakukannya tanpa memunculkan proses tambahan, tanpa harus memisahkan output gabungan, dan tanpa harus menangkap dan mengurai output dari perintah apa pun:

# 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.
sanpath
sumber
Bagus. Saya belum pernah mendengar DETIK sebelumnya
Bruno9779
Apakah Anda kebetulan mulai dengan versi Bash mana yang diperkenalkan?
0xC0000022L
4

Untuk tujuan itu mungkin lebih baik menggunakan times(daripada time) di bashmana mencetak akumulasi pengguna dan waktu sistem untuk shell dan untuk proses yang dijalankan dari shell, contoh gunakan:

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

Lihat: help -m timesuntuk info lebih lanjut.

kenorb
sumber
4

Dalam Menyatukan semua respons sebelumnya, saat dalam OSX

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

Anda bisa suka

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"
}
loretoparisi
sumber
1

Instal /bin/time(mis. pacman -S time)

Jadi alih-alih kesalahan saat mencoba -fmenandai:

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

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

Anda benar-benar dapat menggunakannya:

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

Dan dapatkan yang Anda inginkan - waktu dalam variabel (misalnya penggunaan %euntuk waktu yang telah berlalu, untuk pemeriksaan opsi lain man time):

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

echo "$variable"
Grzegorz Wierzowiecki
sumber
/usr/bin/timepada banyak sistem
Basile Starynkevitch
Jika Anda melihat lebih dekat saya menunjukkan hal itu dalam pertanyaan saya. timeadalah shell builtin di Bash (dan mungkin shell lain) tetapi selama path ke timeexecutable ada PATH, kita dapat menggunakan command timeuntuk memastikan kita menjalankan perintah eksternal sebagai lawan dari builtin.
0xC0000022L
1

Sama seperti kepala ketika bekerja dengan pernyataan di atas dan terutama mengingat jawaban Grzegorz dalam pikiran. Saya terkejut melihat pada sistem Xenial Ubuntu 16.04 saya dua hasil ini:

$ 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

Saya tidak memiliki alias alias apa pun, jadi saya tidak tahu mengapa ini terjadi.

Paul Pritchard
sumber
1
timejuga merupakan shell bawaan.
Basile Starynkevitch
Jika Anda ingin memastikan Anda menjalankan perintah, gunakan command, jika Anda ingin memastikan Anda menjalankan builtin, gunakan builtin. Tidak ada keajaiban di sini. Gunakan helpuntuk mencari tahu perintah yang tersedia atau type <command>untuk mencari tahu apa jenisnya <command>(misalnya builtin, perintah eksternal, fungsi atau alias).
0xC0000022L
Basile, kamu benar sekali. Saya belum melihat pentingnya commandperintah itu. Yang mana fakta memastikan bahwa / usr / bin / time dipanggil. Ini berarti bahwa dua pernyataan berikut ini sesuai: $ command time -f %e sleep 4dan$ /usr/bin/time -f %e sleep
Paul Pritchard
0

coba ini, ia menjalankan perintah sederhana dengan argumen dan menempatkan kali $ real $ user $ sys dan mempertahankan kode keluar.

Itu juga tidak bercabang subshell atau menginjak-injak variabel apa pun kecuali sys pengguna nyata, dan sebaliknya tidak mengganggu jalannya skrip

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

misalnya

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

catatan: ini hanya kali perintah sederhana bukan pipa, yang komponennya dijalankan dalam subkulit

Versi ini memungkinkan Anda menentukan $ 1 nama variabel yang harus menerima 3 kali:

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
}

misalnya

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

dan mungkin berguna jika akhirnya dipanggil secara rekursif, untuk menghindari menginjak-injak waktu; tetapi kemudian rus dll harus dinyatakan lokal dalam penggunaannya.

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

Sam Liddicott
sumber