Cách đo thời gian thực hiện chương trình và lưu trữ bên trong một biến


61

Để tìm hiểu các thao tác nhất định trong tập lệnh Bash (v4 +) mất bao lâu, tôi muốn phân tích đầu ra từ timelệnh "riêng biệt" và (cuối cùng) nắm bắt nó trong một biến Bash ( let VARNAME=...).

Bây giờ, tôi đang sử dụng time -f '%e' ...(hay đúng hơn là command time -f '%e' ...do Bash tích hợp), nhưng vì tôi đã chuyển hướng đầu ra của lệnh đã thực thi nên tôi thực sự bị mất về cách tôi sẽ bắt đầu xuất ra timelệnh. Về cơ bản các vấn đề ở đây là để tách các đầu ra của timetừ đầu ra của lệnh thực thi (s).

Điều tôi muốn là chức năng đếm lượng thời gian tính bằng giây (số nguyên) giữa việc bắt đầu một lệnh và hoàn thành lệnh. Nó không phải là timelệnh hoặc tích hợp tương ứng.


Chỉnh sửa: đưa ra hai câu trả lời hữu ích dưới đây, tôi muốn thêm hai làm rõ.

  1. Tôi không muốn vứt bỏ đầu ra của lệnh đã thực thi, nhưng nó thực sự không quan trọng cho dù nó kết thúc trên thiết bị xuất chuẩn hay thiết bị xuất chuẩn.
  2. Tôi thích một cách tiếp cận trực tiếp hơn một cách gián tiếp (nghĩa là bắt đầu ra trực tiếp thay vì lưu trữ nó trong các tệp trung gian).

Các giải pháp sử dụng datecho đến nay đóng lại với những gì tôi muốn.


Cách trực tiếp nhất để lấy dữ liệu và xử lý nó trong khi vẫn để cho nó chạy bình thường sẽ làm điều đó trong một chương trình C sử dụng fork(), execvp()wait3()/wait4(). Đây cuối cùng là những gì thời gian và bạn bè đang làm. Tôi không biết một cách đơn giản để làm điều đó trong bash / perl mà không chuyển hướng đến một tập tin hoặc cách tiếp cận tương tự.
chim cánh cụt359

Có một câu hỏi liên quan mà bạn có thể thấy thú vị đang diễn ra ở đây .
Caleb

@Caleb: cảm ơn. Quả thực rất thú vị. Tuy nhiên, với mục đích của tôi, nó đủ để thực hiện nó trong một kịch bản.
0xC0000022L

Câu trả lời:


85

Để có được đầu ra của timemột var, hãy sử dụng như sau:

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

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

Bạn cũng có thể chỉ yêu cầu một loại thời gian duy nhất, ví dụ: utime:

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

Để có được thời gian bạn cũng có thể sử dụng date +%s.%N, vì vậy hãy lấy nó trước và sau khi thực hiện và tính toán độ lệch:

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

4
Tôi không muốn vứt bỏ đầu ra từ lệnh mặc dù. Vì vậy, tôi đoán khối mã thứ ba của bạn là gần nhất với những gì tôi đã nghĩ trong đầu. Mặc dù tôi sẽ viết cái cuối cùng là DIFF=$((END-START)), sử dụng các biểu thức số học. :) ... cảm ơn vì câu trả lời. +1
0xC0000022L

1
@STATUS_ACCESS_DENIED: Bash không thực hiện số học dấu phẩy động, vì vậy nếu bạn muốn tốt hơn độ phân giải thứ hai ( .%Ntheo mã của binfalse), bạn cần bchoặc tính toán fancier.
Gilles 'SO- ngừng trở nên xấu xa'

@Gilles: Tôi biết. Như tôi đã viết trong số nguyên câu hỏi của tôi là tốt. Không cần phải có độ phân giải cao hơn thứ hai. Nhưng cảm ơn, việc gọi ngày sẽ phải được thay đổi. Tôi đã nhận ra rằng, mặc dù.
0xC0000022L

6
FYI, định dạng ngày% N dường như không hoạt động trên Mac OS X, nó chỉ trả về "N". Ok trên Ubuntu.
David

Lưu ý rằng mặc dù các time (cmd) 2> somethingcông việc chuyển hướng đầu ra thời gian đến file, nhưng nó không có nghĩa (theo tài liệu), không có trong các shell khác timelà từ khóa và có thể được coi là một lỗi . Tôi sẽ không dựa vào nó vì nó có thể không hoạt động trong các phiên bản tương lai của bash.
Stéphane Chazelas

17

Trong bash, đầu ra của timecấu trúc chuyển sang lỗi tiêu chuẩn của nó và bạn có thể chuyển hướng lỗi tiêu chuẩn của đường ống mà nó ảnh hưởng. Vì vậy, hãy bắt đầu với một lệnh ghi vào luồng đầu ra và lỗi : sh -c 'echo out; echo 1>&2 err'. Để không trộn lẫn luồng lỗi của lệnh với đầu ra từ time, chúng ta có thể tạm thời chuyển luồng lỗi của lệnh sang một bộ mô tả tệp khác:

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

Điều này ghi outvào fd 1, errfd 3 và thời gian cho 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:/')

Sẽ dễ chịu hơn khi có errtrên fd 2 và thời gian trên fd 3, vì vậy chúng tôi trao đổi chúng, điều này rất khó khăn vì không có cách nào trực tiếp để trao đổi hai mô tả tệp:

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

Điều này cho thấy cách bạn có thể xử lý hậu quả đầu ra của lệnh, nhưng nếu bạn muốn nắm bắt cả đầu ra của lệnh và thời gian của lệnh, bạn cần phải làm việc chăm chỉ hơn. Sử dụng một tập tin tạm thời là một giải pháp. Trên thực tế, đó là giải pháp đáng tin cậy duy nhất nếu bạn cần nắm bắt cả lỗi tiêu chuẩn của lệnh và đầu ra tiêu chuẩn của nó. Nhưng nếu không, bạn có thể nắm bắt toàn bộ đầu ra và tận dụng thực tế timecó định dạng có thể dự đoán được (nếu bạn sử dụng time -pđể lấy định dạng POSIX hoặc TIMEFORMATbiến cụ thể 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*}

Nếu bạn chỉ quan tâm đến thời gian đồng hồ treo tường, chạy datetrước và sau là một giải pháp đơn giản (nếu không chính xác hơn một chút do mất thêm thời gian để tải lệnh bên ngoài).


Wow, rất nhiều để kiểm tra. Cảm ơn rất nhiều vì đã trả lời rộng rãi. +1.
0xC0000022L

7

Theo thời gian, đầu ra lệnh xuất hiện trên thiết bị xuất chuẩn và thời gian xuất hiện trên thiết bị xuất chuẩn. Vì vậy, để tách chúng, bạn có thể làm:

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

Nhưng, bây giờ thời gian là trong một tập tin. Tôi không nghĩ Bash có thể đặt stderr vào một biến trực tiếp. Nếu bạn không ngại chuyển hướng đầu ra của lệnh ở đâu đó, bạn có thể làm:

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

Khi bạn thực hiện việc này, đầu ra của lệnh sẽ xuất hiện outputfilevà thời gian cần thiết để chạy $FOO.


1
Ồ, tôi đã không nhận ra. Tôi biết timeviết cho thiết bị xuất chuẩn, nhưng tôi không nhận ra rằng nó sẽ hợp nhất thiết bị xuất chuẩn và thiết bị xuất chuẩn của lệnh được thực thi vào thiết bị xuất chuẩn của nó. Nhưng nói chung không có phương pháp trực tiếp (tức là không có tệp trung gian)? +1.
0xC0000022L

3
@STATUS_ACCESS_DENIED: timekhông hợp nhất lệnh của nó stdoutstderr. Cách tôi chỉ ra rằng bạn chỉ cần đầu ra đầu ra của lệnh. Vì bashsẽ chỉ lưu trữ những gì đi ra stdout, bạn phải chuyển hướng. Trong trường hợp bạn có thể thả stderr của lệnh một cách an toàn: thời gian sẽ luôn ở trên dòng stderr cuối cùng. Nếu bạn cần cả hai luồng đầu ra của lệnh, tôi khuyên bạn nên gói nó trong một tập lệnh khác.
bến

6

Nếu bạn đang ở bash(và không sh) và bạn KHÔNG cần độ chính xác của giây phụ, bạn có thể bỏ qua cuộc gọi datehoàn toàn và thực hiện mà không cần sinh ra bất kỳ quy trình bổ sung nào, mà không phải tách đầu ra kết hợp và không phải bắt và phân tích đầu ra từ bất kỳ lệnh nào:

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

Đẹp. Tôi chưa bao giờ nghe nói về GIÂY trước đây
Bruno9779

Bạn có biết bắt đầu với phiên bản Bash nào được giới thiệu không?
0xC0000022L

4

Với mục đích đó, có lẽ tốt hơn là sử dụng times(hơn time) trong bashđó in thời gian tích lũy của người dùng và hệ thống cho trình bao và cho các quy trình chạy từ trình bao, ví dụ sử dụng:

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

Xem: help -m timesđể biết thêm.


4

Trong việc kết hợp tất cả các phản hồi trước đó, khi ở OSX

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

bạn có thể làm như

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

Cài đặt /bin/time(ví dụ pacman -S time)

Vì vậy, thay vì lỗi khi thử -fcờ:

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

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

Bạn thực sự có thể sử dụng nó:

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

Và nhận được những gì bạn muốn - thời gian biến (ví dụ sử dụng %echo thời gian thực trôi qua, để kiểm tra các tùy chọn khác man time):

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

echo "$variable"

/usr/bin/timetrên nhiều hệ thống
Basile Starynkevitch

Nếu bạn nhìn kỹ tôi sẽ chỉ ra điều đó trong câu hỏi của tôi. timelà một shell được dựng sẵn trong Bash (và có lẽ là các shell khác) nhưng miễn là đường dẫn đến timetệp thực thi được đặt vào PATH, chúng ta có thể sử dụng command timeđể đảm bảo chúng ta đang chạy lệnh bên ngoài trái ngược với dựng sẵn.
0xC0000022L

1

Giống như ngẩng cao đầu khi làm việc với các tuyên bố trên và đặc biệt là ghi nhớ câu trả lời của Grzegorz. Tôi đã rất ngạc nhiên khi thấy trên hệ thống Xenial Ubuntu 16.04 của mình hai kết quả này:

$ 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

Tôi không có bất kỳ bí danh nào được đặt và vì vậy tôi không biết tại sao điều này lại xảy ra.


1
timecũng là một vỏ dựng sẵn.
Basile Starynkevitch

Nếu bạn muốn đảm bảo bạn đang chạy lệnh, hãy sử dụng command, nếu bạn muốn đảm bảo bạn đang chạy nội dung, hãy sử dụng builtin. Không có phép thuật ở đây. Sử dụng helpđể tìm ra các lệnh có sẵn hoặc type <command>để tìm ra loại nào <command>(ví dụ: lệnh dựng sẵn, lệnh bên ngoài, hàm hoặc bí danh).
0xC0000022L

Basile, bạn hoàn toàn đúng. Tôi đã không phát hiện ra tầm quan trọng của commandlệnh. Đó là thực tế đảm bảo rằng / usr / bin / time được gọi. Điều này có nghĩa là hai tuyên bố sau đây tương thích: $ command time -f %e sleep 4$ /usr/bin/time -f %e sleep
Paul Pritchard

0

Hãy thử điều này, nó chạy một lệnh đơn giản với các đối số và đặt thời gian $ real $ user $ sys và giữ nguyên mã thoát.

Nó cũng không rẽ nhánh con hoặc chà đạp lên bất kỳ biến nào ngoại trừ sys người dùng thực và không can thiệp vào việc chạy tập lệnh

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

ví dụ

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

lưu ý: nó chỉ nhân với lệnh đơn giản không phải là một đường ống, mà các thành phần của nó được chạy trong một lớp con

Phiên bản này cho phép bạn chỉ định $ 1 tên của các biến sẽ nhận được 3 lần:

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
}

ví dụ

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

và có thể hữu ích nếu cuối cùng nó được gọi là đệ quy, để tránh chà đạp lên thời gian; nhưng sau đó rus vv nên được khai báo cục bộ trong việc sử dụng của họ.

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.