Program yürütme zamanı nasıl ölçülür ve değişken içinde saklanır


61

Bash (v4 +) komut dosyasındaki belirli işlemlerin ne kadar sürdüğünü bulmak için, çıktıyı time"ayrı ayrı" komutundan ayrıştırmak ve (sonunda) bir Bash değişkeninde ( let VARNAME=...) yakalamak istiyorum .

Şimdi, kullanıyorum time -f '%e' ...(daha ziyade command time -f '%e' ...Bash'in yerleşik olması nedeniyle), fakat zaten yürütülen komutun çıktısını yönlendirdiğim için, timekomutun çıktısını yakalamak için nasıl gideceğim konusunda gerçekten kayboldum . Temel olarak burada sorun çıkış ayırmak için bir timekomutun (ler) çıkışından.

İstediğim şey, bir komutun başlatılması ile tamamlanması arasındaki süreyi saniye cinsinden (tamsayılar) sayma işlevidir . timeKomut veya ilgili yerleşik olmak zorunda değildir .


Düzenleme: Aşağıdaki iki yararlı cevap göz önüne alındığında, iki açıklama eklemek istedim.

  1. Yürütülen komutun çıktısını çöpe atmak istemiyorum, ancak stdout'ta mı yoksa stderr'de mi sonuçlanacağı önemli değil.
  2. Dolaylı bir yaklaşımdan dolaysız bir yaklaşımı tercih ederim (yani çıktıları ara dosyalarda saklamak yerine doğrudan yakalamak).

dateŞimdiye kadar kullanan çözüm, istediklerime yaklaşıyor.


Verileri elde etmenin ve normal bir şekilde çalışmasına izin verirken kullanmanın en doğrudan yolu fork(), execvp()ve kullanarak C programında yapmaktır wait3()/wait4(). Nihayetinde zaman ve arkadaşların yaptığı budur. Bir dosyaya veya benzer bir yaklaşıma yönlendirmeden bunu bash / perl ile yapmanın simle yolunun farkında değilim.
penguin359

Burada ilginizi çekebilecek bir soru var .
Caleb

@Caleb: teşekkürler. Gerçekten de ilginç. Ancak, amaçlarıma göre bir komut dosyasında yapmak yeterli.
0xC0000022L

Yanıtlar:


85

timeÇıktısını bir var içine almak için aşağıdakileri kullanın:

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

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

Ayrıca tek bir zaman türü isteyebilirsiniz, örneğin utime:

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

Ayrıca kullanabileceğiniz zaman kazanmak için date +%s.%N, yürütmeden önce ve sonra alın ve farkları hesaplayın:

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

4
Yine de çıktıyı komuttan atmak istemedim. Sanırım üçüncü kod bloğunuz aklımdakilere en yakın olanı. Sonuncusunu DIFF=$((END-START)), aritmetik ifadelerden faydalanarak yazsam da. :) ... Cevap için teşekkürler. +1
0xC0000022L

1
@STATUS_ACCESS_DENIED: Bash kayan nokta aritmetiği yapmaz, bu yüzden ikinci çözünürlükten daha iyi bir sonuç istiyorsanız ( .%Nbinfalse kodunda), ya bcda meraklı hesaplamaları yapmanız gerekir .
Gilles 'SO- kötülük olmayı'

@Gilles: Biliyorum. Benim soruma yazdığım gibi tamsayılar gayet iyi. Saniyeden daha yüksek bir çözünürlüğe sahip olmanıza gerek yok. Ancak teşekkürler, tarih davetinin değiştirilmesi gerekecekti. Bunu fark etmiştim.
0xC0000022L

6
Bilginize,% N biçimlendirme tarihi Mac OS X üzerinde çalışmıyor gibi görünüyor, sadece "N" döndürür. Ubuntu'da tamam.
David,

time (cmd) 2> somethingZamanlama çıktısını yeniden yönlendirmek için yapılan çalışmaların, file(belgelere göre), timebir anahtar kelimenin bulunduğu ve bir hata olarak kabul edilemediği anlamına gelmediğini unutmayın . Gelecekteki sürümlerinde işe yaramayacağına güvenmem bash.
Stéphane Chazelas,

17

Bash olarak, yapının çıktısı timestandart hatasına gider ve etkilediği boru hattının standart hatasını yönlendirebilirsiniz. O halde, çıkış ve hata streamas yazar bir komutla başlayalım: sh -c 'echo out; echo 1>&2 err'. Komutun hata akışını çıktı ile karıştırmamak için, komutun hata akışını timegeçici olarak farklı bir dosya tanımlayıcıya aktarabiliriz:

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

Bu outfd 1, errfd 3 ve fd 2'nin zamanlarıdır:

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

errFd 2 ve fd 3'teki zamanlar için daha keyifli olur , bu yüzden onları değiştiririz, hantaldır çünkü iki dosya tanımlayıcısını değiştirmenin doğrudan bir yolu yoktur:

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

Bu, komutun çıktısını nasıl işleyebileceğinizi gösterir, ancak hem komutun çıktısını hem de zamanını yakalamak istiyorsanız daha çok çalışmanız gerekir. Geçici bir dosya kullanmak bir çözümdür. Aslında, hem komutun standart hatasını hem de standart çıktısını yakalamanız gerekiyorsa tek güvenilir çözüm budur. Ancak, aksi takdirde, tüm çıktıyı yakalayabilir ve timetahmin edilebilir bir formata sahip olmanızın avantajlarından yararlanabilirsiniz ( POSIX formatını veya bash'a özgü değişkeni kullanmak time -piçin kullanıyorsanız ).TIMEFORMAT

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*}

Yalnızca duvar saati süresini önemsiyorsanız, dateönce ve sonra çalıştırmak basit bir çözümdür (harici komutu yüklemek için harcanan fazladan zaman nedeniyle biraz daha kesinse).


Vay, test etmek için çok fazla. Kapsamlı cevap için çok teşekkürler. +1.
0xC0000022L

7

Zamanla, stdout'ta komut çıktısı çıkar ve stderr'de zaman çıkar. Böylece, onları ayırmak için şunları yapabilirsiniz:

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

Fakat şimdi zaman bir dosyada. Bash'in stderr'i doğrudan bir değişkene koyabileceğini sanmıyorum. Komutun çıktısını bir yere yönlendirmeyi sakıncası yoksa, şunları yapabilirsiniz:

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

Bunu yaptığınızda, komutun çıktısı girecek outputfileve çalıştırılması gereken süre girecektir $FOO.


1
Ooh, farkında değildim. timeStderr'e yazdığını biliyordum , ancak yürütülen komutun stdout ile stderr'ünü stdout'una birleştireceğini fark etmemiştim. Fakat genel olarak doğrudan bir yöntem yoktur (yani, bir ara dosya olmadan)? +1.
0xC0000022L

3
@STATUS_ACCESS_DENIED: timekomutlarını stdoutve birleştirmez stderr. Gösterdiğim yol, yalnızca komutun stdout çıktısına ihtiyacınız olduğunu varsayıyordu. Yana bashsadece çıkıyor ne saklar stdout, yeniden yönlendirmek gerekiyor. Komutunuzun stderrini güvenle bırakmanız durumunda: saat her zaman stderr'in son satırında olacaktır. Her iki komutunuzun da çıktı akışlarına ihtiyacınız varsa, onu başka bir komut dosyasında kaydırmanızı öneririm.
marinus

6

Varsanız bash(değil sh) ve ikinci saniye doğruluğa ihtiyacınız YOKTUR, çağrıyı datetamamen atlayıp ek işlemler oluşturmadan, birleştirilmiş çıktıyı ayırmak zorunda kalmadan ve çıktıyı yakalamak ve ayrıştırmak zorunda kalmadan yapmak için aramayı tamamen atlayabilirsiniz. herhangi bir komuttan:

# 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.

Güzel. Daha önce SECONDS duymadım
Bruno9779 22.08.2018

Bunun hangi Bash versiyonuyla tanıtıldığını biliyor musunuz?
0xC0000022L

4

Bu amaçla , kabuklu ve kabuğundan çalışan işlemler için biriken kullanıcı ve sistem zamanlarını basan times(dan time) kullanımı muhtemelen daha iyidir bash, örnek kullanım:

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

Daha help -m timesfazla bilgi için bakınız:


4

OSX'deyken önceki yanıtları bir araya getirme konusunda

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

beğenebilirsin

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

Kur /bin/time(örn. pacman -S time)

Yani -fbayrak çalışırken hata yerine :

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

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

Aslında kullanabilirsiniz:

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

Ve ne istersen elde et - değişkende zaman (örnek, %egeçen sürelerde, diğer seçenekleri kontrol etmek için kullanır man time):

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

echo "$variable"

/usr/bin/timebirçok sistemde
Basile Starynkevitch

Eğer yakından bakarsanız sorumu işaret ettim. timeBash'te (ve muhtemelen diğer mermilerde) bulunan bir kabuktur, ancak timeçalıştırılabilir yolun içinde olduğu sürece , harici komutu yerleşik olarak çalıştırdığımızdan emin olmak PATHiçin kullanabiliriz command time.
0xC0000022L

1

Tıpkı yukarıdaki ifadelerle çalışırken ve özellikle Grzegorz'in cevabını alarak başa çıkarken olduğu gibi. Ubuntu 16.04 Xenial sistemimde bu iki sonucu gördüğüme şaşırdım:

$ 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

Başka takma adlarım yok ve bu yüzden bunun neden olduğunu bilmiyorum.


1
timeaynı zamanda yerleşik bir kabuktur.
Basile Starynkevitch 19:17

Komutu çalıştırdığınızdan emin olmak istiyorsanız, kullanın command, yerleşik'i çalıştırdığınızdan emin olmak için kullanın builtin. Burada sihir yok. Kullanılabilir helpkomutları bulmak veya type <command>ne tür <command>olduğunu bulmak için kullanın (örn. Yerleşik, harici komut, işlev veya takma ad).
0xC0000022L

Basile, oldukça haklısın. commandKomutun önemini fark etmedim . Bu aslında / usr / bin / time adının alınmasını sağlar. Bu, aşağıdaki iki ifadenin uyumlu olduğu anlamına gelir: $ command time -f %e sleep 4ve$ /usr/bin/time -f %e sleep
Paul Pritchard

0

Bunu deneyin, argümanlarla basit bir komut çalıştırır ve $ real $ user $ sys zamanını koyar ve çıkış kodunu korur.

Ayrıca, gerçek kullanıcı sys dışında herhangi bir değişkende alt kabukları çatallamaz ya da trample etmez ve betiğin çalışmasına başka şekilde müdahale etmez.

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

Örneğin

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

not: sadece basit komut, bileşenleri bir alt kabukta çalıştırılan bir boru hattının değil sadece komut satırının

Bu sürüm, 3 kez alması gereken değişkenlerin adını 1 $ olarak belirlemenizi sağlar:

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
}

Örneğin

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

ve zaman zaman sürtünmekten kaçınmak için özyinelemeli olarak çağrılırsa yararlı olabilir; ancak daha sonra rus vb. kullanımlarında yerel olarak ilan edilmelidir.

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.