使用 Bash 计算程序的平均执行时间

Posted

技术标签:

【中文标题】使用 Bash 计算程序的平均执行时间【英文标题】:Calculate average execution time of a program using Bash 【发布时间】:2019-02-28 07:02:20 【问题描述】:

要获得任何可执行文件的执行时间,比如a.out,我可以简单地写成time ./a.out。这将输出一个实时、用户时间和系统时间。

是否可以编写一个bash脚本,多次运行程序并计算并输出平均实际执行时间?

【问题讨论】:

唯一需要注意的是取决于您的代码有多少保留在缓存中,由于缓存,您后续的运行将人为地更快。 【参考方案1】:

您可以编写一个循环并收集time 命令的输出并将其传送到awk 以计算平均值:

avg_time() 
    #
    # usage: avg_time n command ...
    #
    n=$1; shift
    (($# > 0)) || return                   # bail if no command given
    for ((i = 0; i < n; i++)); do
         time -p "$@" &>/dev/null;  2>&1 # ignore the output of the command
                                           # but collect time's output in stdout
    done | awk '
        /real/  real = real + $2; nr++ 
        /user/  user = user + $2; nu++ 
        /sys/   sys  = sys  + $2; ns++
        END    
                 if (nr>0) printf("real %f\n", real/nr);
                 if (nu>0) printf("user %f\n", user/nu);
                 if (ns>0) printf("sys %f\n",  sys/ns)
               '

例子:

avg_time 5 sleep 1

会给你

real 1.000000
user 0.000000
sys 0.000000

这可以很容易地增强为:

在两次执行之间休眠给定的时间 在执行之间随机休眠(在一定范围内)

time -p 的含义来自man time

   -p
      When in the POSIX locale, use the precise traditional format

      "real %f\nuser %f\nsys %f\n"

      (with  numbers  in seconds) where the number of decimals in the
      output for %f is unspecified but is sufficient to express the
      clock tick accuracy, and at least one.

您可能还想查看这个命令行基准测试工具:

sharkdp/hyperfine

【讨论】:

你的方法在我传入实际程序作为参数时会导致错误awk: division by zero,我认为这个错误可能是由舍入错误引起的,因为我的程序执行时间非常小。跨度> 请使用更新后的答案,看看是否有效。另外,告诉我你的命令。 我相信程序现在可以正常运行了。使用time 进行一次任意运行会得到real 0m0.121s,而运行脚本会得到real 0.105000。我用来在我的程序上执行脚本的命令是avg_time 10 ./a.out 小心! N舍入执行时间的除法不精确,请参阅my answer 试试这个:avg_time 1000 sleep .001 !!【参考方案2】:

总执行时间与单次执行时间总和

小心!除以 N 舍入的总和 执行时间不精确!

相反,我们可以除以 N 次迭代的总执行时间(除以 N)

avg_time_alt()  
    local -i n=$1
    local foo real sys user
    shift
    (($# > 0)) || return;
     read foo real; read foo user; read foo sys ; < <(
         time -p for((;n--;)) "$@" &>/dev/null ; ; 2>&1
    )
    printf "real: %.5f\nuser: %.5f\nsys : %.5f\n" $(
        bc -l <<<"$real/$n;$user/$n;$sys/$n;" )

注意:这使用bc 而不是awk 来计算平均值。为此,我们将创建一个临时的bc 文件:

printf >/tmp/test-pi.bc "scale=%d;\npi=4*a(1);\nquit\n" 60

这将计算 60 位小数的 ,然后安静地退出。 (您可以为您的主机调整小数位数。)

演示:

avg_time_alt 1000 sleep .001
real: 0.00195
user: 0.00008
sys : 0.00016

avg_time_alt 1000 bc -ql /tmp/test-pi.bc
real: 0.00172
user: 0.00120
sys : 0.00058

codeforester's function 将在哪里解析:

avg_time 1000 sleep .001
real 0.000000
user 0.000000
sys 0.000000

avg_time 1000 bc -ql /tmp/test-pi.bc
real 0.000000
user 0.000000
sys 0.000000

替代方案,灵感来自 choroba's answer,使用 Linux 的/proc

好的,你可以考虑:

avgByProc()  
    local foo start end n=$1 e=$1 values times
    shift;
    export n;
     
        read foo;
        read foo;
        read foo foo start foo
     < /proc/timer_list;
    mapfile values < <(
        for((;n--;)) "$@" &>/dev/null;
        read -a endstat < /proc/self/stat
        
            read foo
            read foo
            read foo foo end foo
         </proc/timer_list
        printf -v times "%s/100/$e;" $endstat[@]:13:4
        bc -l <<<"$[end-start]/10^9/$e;$times"
    )
    printf -v fmt "%-7s: %%.5f\\n" real utime stime cutime cstime
    printf "$fmt" $values[@]

这是基于/proc:

man 5 proc | grep [su]time\\\|timer.list | sed  's/^/>   /'
            (14) utime  %lu
            (15) stime  %lu
            (16) cutime  %ld
            (17) cstime  %ld
     /proc/timer_list (since Linux 2.6.21)

那么现在:

avgByProc 1000 sleep .001
real   : 0.00242
utime  : 0.00015
stime  : 0.00021
cutime : 0.00082
cstime : 0.00020

utimestime 代表 用户时间系统时间 用于 bash 自己,cutimecstime 代表子用户时间 和 子系统时间 这是最有趣的

注意:在这种情况下 (sleep) 命令不会使用很多资源。

avgByProc 1000 bc -ql /tmp/test-pi.bc
real   : 0.00175
utime  : 0.00015
stime  : 0.00025
cutime : 0.00108
cstime : 0.00032

这变得更清楚... 当然,作为连续访问timer_listself/stat 而不是原子地real(基于 nanosecs)和 c?[su]time 之间的区别>(基于 ticks 即:1/100 秒)可能会出现!

【讨论】:

惊人的洞察力!我非常喜欢你的解决方案。我同意这是一个更好的方法。我希望time -p 给出更准确的数字。 @codeforester 您是否根据/proc/subShellPid/stat 测试了我的替代方案,显示了childs 系统和用户时间?【参考方案3】:

记录执行的开始和结束时间并将差值除以执行次数可能更容易。

#!/bin/bash
times=10
start=$(date +%s)
for ((i=0; i < times; i++)) ; do
    run_your_executable_here
done
end=$(date +%s)
bc -l <<< "($end - $start) / $times"

我使用bc 计算平均值,因为 bash 不支持浮点运算。

为了获得更高的精度,您可以切换到纳秒:

start=$(date +%s.%N)

$end 也是如此。

【讨论】:

'+%s.%N' 替换+%s 会引发错误(standard_in) 1: illegal character: N @mooncow:那么您的date 与我的不同(GNU 8.25)。 在最新的 linux 内核下,在 pure bash 下,您可以: read foo;read foo;read foo foo now foo; &lt;/proc/timer_list 没有任何分叉,然后echo $now 代表 正常运行时间 以纳秒为单位! 如果/proc/timer_list 的权限是-r--r--r-- root root,则是。 @choroba U 可以看看我的Alternative, inspired by choroba's answer,基于/proc【参考方案4】:

来自bashoneliners

适用于将 (,) 转换为 (.) 以支持 i18n 硬编码为 10,根据需要进行调整 仅返回“真实”值,即您最可能想要的值

单线

for i in 1..10; do time $@; done 2>&1 | grep ^real | sed s/,/./ | sed -e s/.*m// | awk 'sum += $1 END print sum / NR'

我做了一个“更完整”的版本

输出每次执行的结果,以便您知道执行了正确的操作 显示每个运行时间,以便您查看异常值

但实际上,如果您需要高级的东西,只需使用超精细。

GREEN='\033[0;32m'
PURPLE='\033[0;35m'
RESET='\033[0m'

# example: perf sleep 0.001
# https://serverfault.com/questions/175376/redirect-output-of-time-command-in-unix-into-a-variable-in-bash
perfFull() 
    TIMEFORMAT=%R                       # `time` outputs only a number, not 3 lines
    export LC_NUMERIC="en_US.UTF-8"     # `time` outputs `0.100` instead of local format, like `0,100`

    times=10

    echo -e -n "\nWARMING UP $PURPLE$@$RESET"
    $@ # execute passed parameters

    echo -e -n "RUNNING $PURPLE$times times$RESET"

    exec 3>&1 4>&2                                   # redirects subshell streams
    durations=()
    for _ in `seq $times`; 
        durations+=(` time $@ 1>&3 2>&4;  2>&1`)   # passes stdout through so only `time` is caputured
    
    exec 3>&- 4>&-                                   # reset subshell streams

    printf '%s\n' "$durations[@]"

    total=0
    for duration in "$durations[@]"; 
        total=$(bc <<< "scale=3;$total + $duration")
    

    average=($(bc <<< "scale=3;$total/$times"))
    echo -e "$GREEN$average average$RESET"

【讨论】:

以上是关于使用 Bash 计算程序的平均执行时间的主要内容,如果未能解决你的问题,请参考以下文章

Bash 计算执行时间

作业

在 Grafana 上使用 InfluxDB 计算平均请求时间

java中如何计算一个程序运行所需的最少时间,最多时间和平均时间?

bash 一个用于计算 bash 中特定数字行的平均值的衬垫

bash的特性之多命令执行