如何有效获取脚本的执行时间?

mtk*_*mtk 523 bash profiling

我想显示脚本的完成时间。

我目前做的是——

#!/bin/bash
date  ## echo the date at start
# the script contents
date  ## echo the date at end
Run Code Online (Sandbox Code Playgroud)

这只是显示脚本开始和结束的时间。是否可以显示像处理器时间/ io 时间等细粒度的输出?

小智 688

只需time在调用脚本时使用:

time yourscript.sh
Run Code Online (Sandbox Code Playgroud)

  • 这会输出三遍:`real`、`user` 和 `sys`。有关这些的含义,请参见 [此处](http://stackoverflow.com/a/556411/2223706)。 (103认同)
  • “真实”可能是人们想知道的 - “真实是挂钟时间 - 从通话开始到结束的时间” (52认同)
  • 有没有办法将标准输出捕获到文件中?例如,类似`time $( ... ) >> out.txt` (9认同)
  • 或者他可以在他的脚本中这样做: echo $SECONDS 假设它是 bash .... (3认同)
  • 您也可以对命令行上的命令序列执行此操作,例如:`time (command1 && command2)` (2认同)
  • @Jon:时间作为内置函数读取命令行的其余部分,因此任何重定向都不会对其输出起作用。您需要大括号:`{time myfunction} &> logs`。 (2认同)
  • 请注意,Bash 内置 `time` 与命令 `/usr/bin/time` 不同,后者提供更详细的输出。 (2认同)

Rob*_*Bos 273

如果time不是一个选项,

start=`date +%s`
stuff
end=`date +%s`

runtime=$((end-start))
Run Code Online (Sandbox Code Playgroud)

或者,如果您需要亚秒级精度并已bc安装,

start=`date +%s.%N`
stuff
end=`date +%s.%N`

runtime=$( echo "$end - $start" | bc -l )
Run Code Online (Sandbox Code Playgroud)

  • 请注意,这仅在您不需要亚秒级精度时才有效。对于某些用途可能是可以接受的,对于其他用途则不是。为了稍微好一点的精度(例如,你仍然调用 `date` 两次,所以你可能在实践中*最好* 获得毫秒精度,并且可能更少),尝试使用 `date +%s.%N`。(`%N` 是自整秒以来的纳秒。) (38认同)
  • 只需安装`bc`,然后执行以下操作:`runtime=$( echo "$end - $start" | bc -l )` (12认同)
  • 如果您的脚本需要几分钟,请使用:`echo "Duration: $((($(date +%s)-$start)/60)) minutes` (12认同)
  • @ChrisH 哦。好指出; bash 算术扩展仅是整数。我看到两个明显的选择;要么丢弃日期格式字符串中的句点(并将结果值视为自纪元以来的纳秒),所以使用 `date +%s%N`,或者使用更强大的东西,比如 `bc` 从两个值计算实际运行时间就像 jwchew 建议的那样。尽管如此,我还是觉得这种方法不是最理想的做法;出于上述原因,如果可用,`time` 会好得多。 (5认同)
  • 除了@rubo77 评论,如果您的脚本需要几个小时,请使用 `hours=$((runtime / 3600)); 分钟=$(((运行时% 3600) / 60 )); 秒=$(((运行时%3600)%60));echo "运行时间:$hours:$minutes:$seconds (hh:mm:ss)"` (4认同)
  • 这很好,但是随着@MichaelKjörling的改进,我收到以下错误:`bash:1390472071.282341976:语法错误:无效的算术运算符(错误标记为“.282341976”)`,所以也许这需要更多澄清。 (2认同)

Sté*_*las 95

times退出脚本时无需参数即可调用。

使用kshzsh,您也可以time改用。使用zsh,time除了用户系统CPU 时间之外,还将为您提供挂钟时间。

要保留脚本的退出状态,您可以:

ret=$?; times; exit "$ret"
Run Code Online (Sandbox Code Playgroud)

或者您也可以在EXIT以下位置添加陷阱:

trap times EXIT
Run Code Online (Sandbox Code Playgroud)

这样,每当 shell 退出时都会调用时间并保留退出状态。

$ 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
Run Code Online (Sandbox Code Playgroud)

另请注意,所有bash,kshzsh都有一个$SECONDS特殊变量,每秒自动递增。在zsh和 中ksh93,该变量也可以设为浮点数(与typeset -F SECONDS)以获得更高的精度。这只是挂钟时间,而不是 CPU 时间。

  • $SECONDS 变量非常有用,谢谢! (17认同)
  • 嗨,@Stéphane Chazelas。我发现`times` 没有时间,对吧?例如,`bash -c 'trap times EXIT;sleep 2'` (3认同)

小智 39

我有点晚了,但想发布我的解决方案(亚秒级精度),以防其他人通过搜索偶然发现这个线程。输出格式为天、小时、分钟,最后是秒:

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
Run Code Online (Sandbox Code Playgroud)

希望有人在那里发现这很有用!


Mar*_*ark 35

我的方法bash

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

  • 那些寻找所提出问题的答案的人可能会发现此解决方案很有用。您的评论最好针对 OP 问题提出。 (8认同)
  • 这确实显示了经过的时间,但它没有显示问题中要求的“细粒度输出,如处理器时间、I/O 时间”。 (2认同)

小智 19

就个人而言,我喜欢将所有脚本代码包装在一些“main”函数中,如下所示:

main () {
 echo running ...
}

# stuff ...

# calling the function at the very end of the script
time main
Run Code Online (Sandbox Code Playgroud)

请注意time在这种情况下使用该命令是多么容易。显然,您没有测量包括脚本解析时间在内的精确时间,但我发现它在大多数情况下都足够准确。

  • 这是最好的解决方案。唯一的问题是您无法从“main”中访问脚本参数。要将参数传递给函数,您需要在最后一行调用 `time main "$@"` (6认同)

小智 11

#!/bin/bash
start=$(date +%s.%N)

# HERE BE CODE

end=$(date +%s.%N)    
runtime=$(python -c "print(${end} - ${start})")

echo "Runtime was $runtime"
Run Code Online (Sandbox Code Playgroud)

是的,这称为 Python,但如果您能接受它,那么这是一个非常不错的简洁解决方案。

  • “几百万纳秒”是几毫秒。为 bash 脚本计时的人通常不太关心这一点(除非他们每兆秒运行数十亿个脚本)。 (12认同)
  • 请注意,在大多数当前系统上,在子 shell 中运行该 `python` 命令并读取其输出将花费数百万纳秒。运行`date` 也是如此。 (6认同)
  • 还要注意的是,即使计算是在 python 进程内完成的,在调用 python 之前,这些值也是完全收集的。脚本的执行时间将被正确测量,没有“数百万纳秒”的开销。 (5认同)

zba*_*son 11

这个问题已经很老了,但在试图找到我最喜欢的方法时,这个线程出现了很高的......我很惊讶没有人提到它:

perf stat -r 10 -B sleep 1
Run Code Online (Sandbox Code Playgroud)

'perf' 是内核中包含在 'tools/perf' 下的性能分析工具,通常可以作为单独的软件包安装(CentOS 中的“perf”和 Debian/Ubuntu 上的“linux-tools”)。 Linux Kernal perf Wiki有更多关于它的信息。

运行 'perf stat' 给出了很多细节,包括最后的平均执行时间:

1.002248382 seconds time elapsed                   ( +-  0.01% )
Run Code Online (Sandbox Code Playgroud)

  • 什么是“性能”? (3认同)

Evg*_*eny 7

可以在命令之前添加一个小的 shell 函数来测量它们的时间:

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

然后在您的脚本或命令行中使用它,如下所示:

tm the_original_command with all its parameters
Run Code Online (Sandbox Code Playgroud)

  • @loretoparisi 几毫秒,你可以试试`date +%s%N`。见 https://serverfault.com/questions/151109/how-to-get-current-unix-time-in-milliseconds-in-bash/588705#588705 (2认同)

Jue*_*gen 7

接受的解决方案使用timewrites to stderr。使用写入的
解决方案。 使用的解决方案缺少亚秒级精度。 其他解决方案涉及调用外部程序,例如或 ,但效率不高。 如果您对其中任何一个都满意,请使用它。timesstdout
$SECONDS
dateperf

但是,如果您需要一个有效的解决方案来获取毫秒精度的时间,并需要将它们放入变量中,以便原始输出保持不受干扰,您可以将进程替换与一些重定向结合起来,time这比调用外部程序要快得多,并且允许围绕计时包装器进行重定向脚本与原始命令/脚本相同。

# 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
Run Code Online (Sandbox Code Playgroud)

选择以下变体之一,解析time适合您需要的输出:
最短变体,其中stdoutof$Cmd被写入stderr且不写入任何内容stdout

read Elapsed User System < <({ time $Cmd 2>&3; } 3>&2 2>&1 >&3)
Run Code Online (Sandbox Code Playgroud)

更长的变体,保持原始stdoutstderr彼此分离:

{ read Elapsed User System < <({ time $Cmd 2>&4; } 4>&2 2>&1 >&3); } 3>&1
Run Code Online (Sandbox Code Playgroud)

最复杂的变体包括关闭额外的文件描述符,这样就$Cmd好像没有这个定时包装器一样调用它,并且lvm命令vgs不抱怨泄漏的文件描述符:

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

bash您甚至可以在不调用的情况下伪造浮点加法bc,这会慢得多:

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
Run Code Online (Sandbox Code Playgroud)

$Cmd在慢速 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
Run Code Online (Sandbox Code Playgroud)

或者:

stdout
stderr
CPU 0.008 s, Elapsed 0.109 s
Run Code Online (Sandbox Code Playgroud)


小智 5

#!/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 shihcheng.guo@gmail.com
#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..."
Run Code Online (Sandbox Code Playgroud)

  • 这与已经给出的其他答案有什么不同,这些答案在脚本之前和之后使用`date`并输出它们之间的差异? (7认同)

Gab*_*les 5

  1. 只需使用time [any command]. 例如:time sleep 1将实时睡眠(即:由秒表计时)约 1.000 到约 1.020 秒,如下所示:

     $ time sleep 1
    
     real    0m1.011s
     user    0m0.004s
     sys 0m0.000s
    
    Run Code Online (Sandbox Code Playgroud)

    多么美好的事情。你可以在它后面放置任何命令,它会以一种漂亮的、人类可读的形式输出结果。我真的很喜欢将它用于计时构建。前任:

     # time your "make" build
     time make
    
     # time your "Bazel" build
     time bazel build //path/to/some:target
    
    Run Code Online (Sandbox Code Playgroud)

    ...或者对于可能很长的 git 操作,所以我可以制定切合实际的心理期望:

     # 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
    
    Run Code Online (Sandbox Code Playgroud)
  2. 对于更自定义的计时需求,您可能需要在 bash 中操作输出或将其转换为其他形式,请使用内部$SECONDS变量。这是一个演示,包括将这些秒转换为其他单位,例如浮点分钟:

    请注意,在这种情况下dt_min0.01666666666...(1 秒 = 那么多分钟)四舍五入,0.017因为我正在使用该printf函数进行四舍五入。sleep 1;下面的部分是您调用脚本运行和计时的地方,但为了这个演示,我只是睡了 1 秒钟。

    命令:

     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"
    
    Run Code Online (Sandbox Code Playgroud)

    输出:

     dt_sec = 1; dt_min = 0.017
    
    Run Code Online (Sandbox Code Playgroud)

有关的:

  1. 在这里阅读更多关于bcprintf我的回答:https : //stackoverflow.com/questions/12722095/how-do-i-use-floating-point-division-in-bash/58479867#58479867
  2. 我不记得我第一次从哪里了解到该time命令,但它可能来自@Trudbert 的回答就在这里


bra*_*blc 5

使用内置的 bash 时间吗?

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.
Run Code Online (Sandbox Code Playgroud)

例子:

TIMEFORMAT="The command took %Rs"
time {
    sleep 0.1
}
Run Code Online (Sandbox Code Playgroud)

输出:

The command took 0.108s
Run Code Online (Sandbox Code Playgroud)