วิธีการวัดเวลาของการดำเนินการของโปรแกรมและเก็บไว้ในตัวแปร


61

เพื่อค้นหาว่าการดำเนินการบางอย่างภายในสคริปต์ Bash (v4 +) ใช้เวลานานเท่าใดฉันต้องการแยกเอาต์พุตจากtimeคำสั่ง "แยก" และ (ท้ายที่สุด) จับภาพภายในตัวแปร Bash ( let VARNAME=...)

ตอนนี้ฉันกำลังใช้time -f '%e' ...(หรือมากกว่าcommand time -f '%e' ...เพราะในตัวของ Bash) แต่เนื่องจากฉันเปลี่ยนเส้นทางเอาต์พุตของคำสั่งที่ดำเนินการไปtimeแล้ว โดยทั่วไปปัญหาที่นี่คือที่จะแยกการส่งออกของtimeจากผลลัพธ์ของคำสั่งประหารชีวิต (s)

สิ่งที่ฉันต้องการคือฟังก์ชั่นการนับจำนวนเวลาเป็นวินาที (จำนวนเต็ม) ระหว่างการเริ่มต้นคำสั่งและความสมบูรณ์ มันไม่จำเป็นต้องเป็นtimeคำสั่งหรือในตัวที่เกี่ยวข้อง


แก้ไข:เนื่องจากคำตอบที่มีประโยชน์สองคำตอบด้านล่างนี้ฉันต้องการเพิ่มคำอธิบายสองคำ

  1. ฉันไม่ต้องการทิ้งเอาต์พุตของคำสั่งที่เรียกใช้แล้ว แต่มันจะไม่สำคัญว่ามันจะจบลงด้วย stdout หรือ stderr
  2. ฉันต้องการวิธีการโดยตรงมากกว่าหนึ่งทางอ้อม (เช่นการจับเอาท์พุทโดยตรงแทนที่จะเก็บไว้ในไฟล์ระดับกลาง)

การแก้ปัญหาโดยใช้dateมาไกลถึงสิ่งที่ฉันต้องการ


วิธีที่ตรงที่สุดที่จะได้รับข้อมูลและจัดการกับมันขณะที่ยังคงปล่อยให้มันทำงานได้ตามปกติจะทำมันในโปรแกรม C ใช้fork(), และexecvp() wait3()/wait4()นี่คือสิ่งที่ในที่สุดเวลาและเพื่อนกำลังทำ ฉันไม่ทราบวิธีที่ง่ายในการทำใน bash / perl โดยไม่เปลี่ยนเส้นทางไปยังไฟล์หรือวิธีการที่คล้ายกัน
penguin359

มีคำถามที่เกี่ยวข้องที่คุณอาจพบที่น่าสนใจคือเกิดขึ้นที่นี่
Caleb

@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ได้ดังนั้นก่อนและหลังการทำงานและคำนวณ diff:

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

@Gilles: ฉันรู้ ในขณะที่ฉันเขียนในคำถามของฉันจำนวนเต็มดี ไม่จำเป็นต้องมีความละเอียดสูงกว่าวินาที แต่ต้องขอขอบคุณการเปลี่ยนวันที่จะต้องมีการเปลี่ยนแปลง ฉันได้ตระหนักว่าแม้ว่า
0xC0000022L

6
ปีงบประมาณการจัดรูปแบบวันที่% N ดูเหมือนจะไม่ทำงานบน Mac OS X แต่จะคืนค่าเป็น "N" ตกลงบน Ubuntu
เดวิด

โปรดทราบว่าในขณะที่time (cmd) 2> somethingผลงานในระยะเวลาเปลี่ยนเส้นทางออกไปfileก็ไม่ได้หมายถึง (ตามเอกสาร) ไม่ได้ในเปลือกหอยอื่น ๆ ที่timeเป็นคำหลักและอาจจะถือเป็นข้อบกพร่อง bashฉันจะไม่พึ่งพามันขณะที่มันอาจไม่ทำงานในรุ่นอนาคตของ
Stéphane Chazelas

17

ใน bash เอาต์พุตของการtimeสร้างไปที่ข้อผิดพลาดมาตรฐานและคุณสามารถเปลี่ยนทิศทางข้อผิดพลาดมาตรฐานของไปป์ไลน์ที่ส่งผลกระทบ ดังนั้นขอเริ่มต้นด้วยคำสั่งที่เขียนเพื่อการส่งออกและความผิดพลาดของ sh -c 'echo out; echo 1>&2 err'streamas: เพื่อไม่ให้รวมกระแสข้อผิดพลาดของคำสั่งกับเอาท์พุทจาก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 ดังนั้นเราจึงสลับมันซึ่งยุ่งยากเพราะไม่มีวิธีโดยตรงในการสลับสองไฟล์ descriptors:

{ { { 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ในหลาย ๆ ระบบ
Basile Starynkevitch

หากคุณมองอย่างใกล้ชิดฉันชี้ไปที่คำถามของฉัน timeเป็น shell builtin ใน Bash (และเชลล์อื่น ๆ น่าจะเป็น) แต่ตราบใดที่พา ธ ไปยังtimeexecutable อยู่PATHเราสามารถใช้command timeเพื่อให้แน่ใจว่าเรากำลังเรียกใช้คำสั่งภายนอกเมื่อเทียบกับ builtin
0xC0000022L

1

เช่นเดียวกับหัวเมื่อทำงานกับข้อความข้างต้นและโดยเฉพาะการตอบคำถามของ Grzegorz ฉันรู้สึกประหลาดใจที่เห็นบน 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เป็นเปลือกในตัว
Basile Starynkevitch

ถ้าคุณต้องการเพื่อให้แน่ใจว่าคุณกำลังเรียกใช้คำสั่งให้ใช้ถ้าคุณต้องการเพื่อให้แน่ใจว่าคุณกำลังใช้งานในตัวการใช้command builtinไม่มีเวทย์มนตร์ที่นี่ ใช้helpเพื่อหาคำสั่งที่มีอยู่หรือtype <command>เพื่อหาว่า<command>เป็นประเภทใด (เช่นคำสั่งในตัวคำสั่งภายนอกฟังก์ชันหรือนามแฝง)
0xC0000022L

Basile คุณพูดถูก ฉันไม่เห็นความสำคัญของcommandคำสั่ง ซึ่งเป็นข้อเท็จจริงทำให้มั่นใจได้ว่า / usr / bin / เวลาถูกเรียก ซึ่งหมายความว่าสองคำสั่งต่อไปนี้เข้ากันได้$ command time -f %e sleep 4และ$ /usr/bin/time -f %e sleep
พอลพริทชาร์ด

0

ลองนี้มันรันคำสั่งง่าย ๆ ที่มีข้อโต้แย้งและทำให้เวลา $ real $ user $ sys และรักษารหัสทางออก

นอกจากนี้ยังไม่แยกย่อยหรือเหยียบย่ำในตัวแปรใด ๆ ยกเว้น 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

หมายเหตุ: มันเพียงครั้งเดียวคำสั่งง่าย ๆ ไม่ได้ไปป์ไลน์ซึ่งมีองค์ประกอบที่จะทำงานใน subshell

รุ่นนี้ให้คุณระบุ $ 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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.