如何测量 bash 命令之间的执行时间差异?

How to measure the difference in execution time between bash commands?

提问人:Uberhumus 提问时间:11/18/2023 最后编辑:Uberhumus 更新时间:11/18/2023 访问量:120

问:

当给定两个 bash 命令时,如何测量执行时间的差异?理想情况下,我希望这是有方向性的,而不是绝对的。毫秒级精度是首选。command1command2

我可以通过代码来实现这一点,但相比之下,我这样做的方式感觉非常麻烦。

#!/bin/env bash

command1=$1
command2=$2
time1=$(/usr/bin/time -f "%E" $command1 2>&1 >/dev/null |\
 awk -F: '{secs=$3; secs+=$2*60; sec+=$3*3600; printf "%.3f\n", secs*1000}')
time2=$(/usr/bin/time -f "%E" $command2 2>&1 >/dev/null |\
 awk -F: '{secs=$3; secs+=$2*60; sec+=$3*3600; printf "%.3f\n", secs*1000}')
time_diff=$(echo "$time1 - $time2" | bc)
echo $time_diff
Bash 性能

评论

0赞 Cyrus 11/18/2023
如果输出精确到秒,是否足够?
0赞 Uberhumus 11/18/2023
@Cyrus,不是真的,我更新了问题以澄清。如果这样的解决方案足够有趣并教会了我一些东西,我仍然会投票。

答:

4赞 KamilCuk 11/18/2023 #1

为什么不只是:

start=$(date +%s.%N)
command1
mid=$(date +%s.%N)
command2
stop=$(date +%s.%N)
time_diff=$(bc -l <<<"($mid - $start) - ($stop - $mid)")
echo "$time_diff"

或者更好

start=$EPOCHREALTIME
command1
mid=$EPOCHREALTIME
command2
stop=$EPOCHREALTIME
time_diff=$(bc -l <<<"($mid - $start) - ($stop - $mid)")
echo "$time_diff"

评论

2赞 Léa Gris 11/18/2023
由于您使用的是特定于 Bash 的重定向,因此请使用特定于 Bash 的变量: 由于没有生成日期命令子 shell,因此测量更准确。LC_NUMERIC=C; start=$EPOCHREALTIME; command1; mid=$EPOCHREALTIME; command2; stop=$EPOCHREALTIME; ...
0赞 Uberhumus 11/18/2023
卡米尔,如果我能问,为什么不呢?为什么你更喜欢用 or 代替 or ?不是批评,要求学习。bc -lbchere stringechohere doc
1赞 KamilCuk 11/18/2023
why bc -l instead of bc?默认小数位数=0,因此在默认情况下,逗号后没有数字。 生成一个单独的进程,所以速度较慢,这里 doc 需要输入更多。bcbc -lscale=20why did you prefer to use here stringecho
2赞 Walter A 11/18/2023 #2

避免并使用内置的 .datetimeSECONDS

SECONDS=0
command1
firststop=$SECONDS
SECONDS=0
command2
echo "Diff $((SECONDS - firststop))."

SECONDS=0
sleep 4
firststop=$SECONDS
SECONDS=0
sleep 7
echo "Diff $((SECONDS - firststop))."
# Returns...
Diff 3.

评论

0赞 Uberhumus 11/18/2023
这将丢失 的毫秒数或 的纳秒数。我不认为纳秒特别有意义,但如果我多次运行它并聚合数据,毫秒可能会被证明是有意义的。我认为。timedate
3赞 Ed Morton 11/18/2023 #3
$ begs[1]=$EPOCHREALTIME; sleep 1; ends[1]=$EPOCHREALTIME
$ begs[2]=$EPOCHREALTIME; sleep 3; ends[2]=$EPOCHREALTIME
$ awk -v bs="${begs[*]}" -v es="${ends[*]}" 'BEGIN {
    split(bs,begs)
    split(es,ends)
    for (i in begs) {
        durs[i] = ends[i] - begs[i]
    }
    print durs[2] - durs[1]
}'
2.0128

但是,如果要分析一堆命令的时间,请考虑编写一个小脚本来执行每个命令并打印计时信息,例如:

$ cat tst.sh
#!/usr/bin/env bash

cnt=0
while IFS= read -p 'Cmd> ' -r cmd; do
    if [[ "$cmd" == "." ]]; then
        break
    fi
    cnt=$(( cnt + 1 ))
    cmds["$cnt"]="$cmd"
    begs["$cnt"]=$EPOCHREALTIME
    eval "$cmd"
    ends["$cnt"]=$EPOCHREALTIME
done

awk '
    FILENAME == ARGV[1] { begs[FNR] = $0; next }
    FILENAME == ARGV[2] { ends[FNR] = $0; next }
    {
        cmds[FNR] = $0
        durs[FNR] = ends[FNR] - begs[FNR]

        print  "---"
        printf "Cmd = %s\n", cmds[FNR]
        printf "Dur = %f\n", durs[FNR]
        printf "Chg = %f\n", (FNR>1 ? durs[FNR] - durs[FNR-1] : 0)
    }' \
        <(printf '%s\n' "${begs[@]}") \
        <(printf '%s\n' "${ends[@]}") \
        <(printf '%s\n' "${cmds[@]}") \
            >&2

您可以按如下所示运行,并获得如下所示的输出:

$ ./tst.sh
Cmd> date -d today +'%F %T'
2023-11-18 06:22:38
Cmd> sleep 1
Cmd> seq 10000000 > /dev/null
Cmd> echo "$(date)"
Sat Nov 18 06:23:14 CST 2023
Cmd> sleep 2
Cmd> .
---
Cmd = date -d today +'%F %T'
Dur = 0.019273
Chg = 0.000000
---
Cmd = sleep 1
Dur = 1.021269
Chg = 1.001996
---
Cmd = seq 10000000 > /dev/null
Dur = 0.341198
Chg = -0.680071
---
Cmd = echo "$(date)"
Dur = 0.023039
Chg = -0.318159
---
Cmd = sleep 2
Dur = 2.019530
Chg = 1.996491

评论

0赞 Ed Morton 11/19/2023
对不起,但我不知道你的评论是什么意思。但是,如果您有后续问题,那么只需提出一个新问题,而不是将其放在评论中。
0赞 Uberhumus 11/19/2023
我认为,如果你开始扩展它的功能,它会更好地实现为编译的二进制文件。你觉得怎么样?
1赞 Ed Morton 11/19/2023
不一定,这取决于它做什么,每秒要调用多少次,等等。不要假设编译的东西比解释的东西运行得更快——情况并非总是如此。解释的 awk 脚本通常比编译的 C 程序更快,例如,因为 awk 中内置的所有用于读取输入、拆分字段、动态调整数组内存大小、实现哈希表等的代码都在 awk 命令中进行了高度优化,而个人必须自己用 C 语言编写它,并且可能不会做得那么好。
0赞 Ed Morton 11/19/2023
即使解释脚本比编译的二进制文件慢,它几乎肯定足够快,可以满足您的需求(并且需要的编码要少得多),除非在某个需要以极快速度运行的系统中每秒调用多次。如果它太慢,很有可能是因为你在写它时做错了什么,并且有一个更快的、仍然被解释的替代方案(例如,如果你写了一个 shell while-read 循环来操作文本,而不是调用 awk 来更快地完成它)。
1赞 ufopilot 11/18/2023 #4
$ command1(){ sleep 5; }
$ command2(){ sleep 2; }

$ { (time command1) 2>&1; (time command2) 2>&1; } | 
    awk -F'real *|m|s' '
        /real/{
            a[i+=1]=($2*60)+$3
        }
        END{
            print "time1:", a[1]
            print "time2:", a[2]
            print "diff:", a[1]-a[2]
        }
    ' 
time1: 5.008
time2: 2.011
diff: 2.997