通用的非bash时间基准替代方案?[关闭]


10

为了比较不同外壳之间脚本的运行时间,一些SE答案建议使用bash内置 time命令,如下所示:

time bash -c 'foo.sh'
time dash -c 'foo.sh'

... ,每个要测试的外壳。这样的基准测试无法消除每个shell加载和初始化自身所花费的时间。例如,假设上述两种命令被存储在一缓慢的设备具有早期软盘的读取速度,(124KB /秒), dash(一〜150K可执行程序)将加载大约7倍的速度比bash〜1M),所述壳加载时间会使time数字产生偏差-这些外壳的预加载时间与加载foo.sh外壳测量每个外壳下的运行时间无关。

什么是可以每个Shell中运行的用于脚本计时的最佳便携式和通用工具?因此,上面的代码如下所示:

bash -c 'general_timer_util foo.sh'
dash -c 'general_timer_util foo.sh'

注意:因为没有可移植的或通用的命令,所以没有外壳内置 time命令。


更好的是,该实用程序还能够对Shell内部命令和管道所花费的时间进行基准测试,而无需用户先将它们包装在脚本中。这样的人工语法会有所帮助:

general_timer_util "while read x ; do echo x ; done < foo"

一些shell time可以管理这个。例如bash -c "time while false ; do : ; done"作品。要查看在您的系统上哪些有效(哪些无效),请尝试:

tail +2 /etc/shells | 
while read s ; do 
    echo $s ; $s -c "time while false ; do : ; done" ; echo ----
done

6
只是使用/usr/bin/time
库沙兰丹

1
我不明白,任何非内建函数都可能既“消除每个shell加载和初始化自身所花费的时间”又执行一个独立的脚本同时又“可移植且通用”。
Michael Homer

1
这不是问题的答案,而是提示您澄清您想要的内容。
Michael Homer'1

1
我已尽力而为,但我认为问题仍未明确其实际要实现的目标。
Michael Homer

2
您所说的“便携式或通用”是什么意思?Shell内置程序与外部命令一样具有可移植性(可在许多系统上工作),并且更通用(可在更多环境下工作,因为它们可以计时而不是执行文件)。您要解决什么问题?
吉尔斯(Gilles)'“ SO-别再邪恶了”

Answers:


10

您应该注意timePOSIX和AFAICT 指定了POSIX提到的唯一选项,-p各种外壳程序均正确支持该选项:

$ bash -c 'time -p echo'

real 0.00
user 0.00
sys 0.00
$ dash -c 'time -p echo'

real 0.01
user 0.00
sys 0.00
$ busybox sh -c 'time -p echo'

real 0.00
user 0.00
sys 0.00
$ ksh -c 'time -p echo'       

real 0.00
user 0.00
sys 0.00

1
问题在于,为了能够比较时序,必须通过的相同实现对结果进行计时time。这相当于让短跑运动员单独测量自己在100m上的时间,而不是同时测量一个时钟。这显然是挑剔的,但仍然...
库萨兰达

@Kusalananda我以为问题在于OP的思想time是不可移植的。它似乎是便携式的。(不过,我同意您关于可比性的观点)
muru

@muru,在我的系统上dash -c 'time -p while false ; do : ; done'返回“时间:无法运行,同时:没有此类文件或目录<cr>命令以非零状态127退出”错误。
agc

1
@agc POSIX还说:“使用实用程序一词,而不是命令,是为了强调不能直接使用外壳复合命令,管道,特殊内置程序等事实。但是,实用程序包括用户应用程序和Shell脚本,而不仅仅是标准实用程序。” (请参阅“理据”部分)
muru


7

我使用GNU date命令,该命令支持高分辨率计时器:

START=$(date +%s.%N)
# do something #######################

"$@" &> /dev/null

#######################################
END=$(date +%s.%N)
DIFF=$( echo "scale=3; (${END} - ${START})*1000/1" | bc )
echo "${DIFF}"

然后我这样调用脚本:

/usr/local/bin/timing dig +short unix.stackexchange.com
141.835

输出单位以毫秒为单位。


1
假设时间(纪元时间)在两者之间不变。在实践中无法想到会导致问题的情况,但仍然值得一提。
phk

1
您应该补充一点,这特别需要GNU date
Kusalananda

@phk请解释吗?
拉宾

1
@Rabin假设您的NTP客户端发出问题并在STARTEND设置之间更新和更改时钟,那么这显然会影响您的结果。虽然不知道您需要多少精确度以及它是否对您的情况重要,但是就像我说的那样,请记住一点。(有趣的故事:我知道一个软件在哪里导致了意外的负面结果–它曾用于吞吐量计算–然后破坏了一些东西。)
phk

1
另外,不是某些NTP客户端会减慢速度并加快时钟速度,而不是在系统时间中“跳”一下吗?如果您有这样的NTP客户端,并且您在昨天晚上进行了一些计时,则NTP客户端“预期” second秒可能会扭曲它们。(或者在这种情况下系统时钟是否仅运行到61?)
JörgW Mittag

6

time如您所注意到的,该实用程序通常内置在外壳中,这使其无法用作“中立”计时器。

但是,该实用程序通常也可以作为外部实用程序/usr/bin/time使用,可以很好地用于执行您建议的计时实验。

$ bash -c '/usr/bin/time foo.sh'

这如何“消除每个shell加载和初始化自身所花费的时间”?
Michael Homer'1

1
如果foo.sh是可执行文件并具有Shebang,则它将始终在同一Shell中运行,并且它确实计算了该Shell的启动时间,因此这不是OP想要的。如果foo.sh缺少其中之一,则根本不起作用。
凯文(Kevin)

@凯文非常正确。time看来,我只考虑了“没有内置shell ”。外壳启动时间可能必须单独测量。
Kusalananda

1
我不知道任何具有time内置命令的外壳。但是,许多shell都包含可用于计时管道bashtime关键字。要禁用该关键字以便使用time命令(在文件系统中),可以像这样引用它"time" foo.sh。又见unix.stackexchange.com/search?q=user%3A22565+time+keyword
斯特凡Chazelas

6

这里是一个解决方案:

  1. 消除每个外壳程序加载和初始化所需的时间

  2. 可以每个shell中运行

  3. 用途

    没有外壳内置time命令,因为它们都不是可移植的或通用的

  4. 在所有POSIX兼容的外壳程序中均可使用。
  5. 使用C编译器在所有POSIX兼容且符合XSI的系统上工作,或者您可以预先编译C可执行文件。
  6. 在所有shell上使用相同的计时实现。

它由两部分组成:一个简短的C程序gettimeofday,该程序包装了,但不推荐使用,但仍比clock_gettime该程序具有更多的可移植性;一个简短的Shell脚本,使用该程序获取一个微秒级的时钟,以读取源代码的两面。C程序是在时间戳上获得亚秒级精度的唯一可移植且开销最小的方法。

这是C程序epoch.c

#include <sys/time.h>
#include <stdio.h>
int main(int argc, char **argv) {
    struct timeval time;
    gettimeofday(&time, NULL);
    printf("%li.%06i", time.tv_sec, time.tv_usec);
}

和shell脚本timer

#!/bin/echo Run this in the shell you want to test

START=$(./epoch)
. "$1"
END=$(./epoch)
echo "$END - $START" | bc

这是标准的shell命令语言bc在任何POSIX兼容的shell下都应作为脚本运行。

您可以将其用作:

$ bash timer ./test.sh
.002052
$ dash timer ./test.sh
.000895
$ zsh timer ./test.sh
.000662

它不测量系统或用户时间,仅测量非单调的挂钟经过时间。如果在脚本执行过程中系统时钟发生变化,则会产生错误的结果。如果系统处于负载状态,则结果将不可靠。我认为没有更好的东西可以在外壳之间移植。

修改后的计时器脚本可以用来eval在脚本外部运行命令。


碰巧的是,在阅读本文之前(以及关于的最后一行eval),我正在Rabin的答案中的脚本调整为include eval "$@",以便它可以动态运行shell 内置程序
agc

4

借助agc的输入,多次使用/proc/uptimedc/ bc/ 多次修订了解决方案:awk

#!/bin/sh

read -r before _ < /proc/uptime

sleep 2s # do something...

read -r after _ < /proc/uptime

duration=$(dc -e "${after} ${before} - n")
# Alternative using bc:
#   duration=$(echo "${after} - ${before}" | bc)
# Alternative using awk:
#   duration=$(echo "${after} ${before}" | awk '{print $1 - $2}')

echo "It took $duration seconds."

假定/proc/uptime存在并具有某种形式。


3
这将使其在外壳程序之间可移植,但在Unix实现之间则不可移植,因为某些应用程序只是缺少/proc文件系统。是否要担心,我不知道。
Kusalananda

1
为强调起见,此Q建议使用软盘速度或更慢的速度。在这种情况下,加载的开销awk可能会很大。也许b=$(cat /proc/uptime)之前,a=$(cat /proc/uptime)之后,然后解析$ a$ b并相减。
agc

@agc很好的输入,谢谢,我相应地添加了一些替代解决方案。
phk

1
以前没想过,但是如果像这样的内建函数read比更好cat,它将更干净(并且速度更快): read before dummyvar < /proc/uptime ;sleep 2s;read after dummyvar < /proc/uptime; duration=$(dc -e "${after} ${before} - n");echo "It took $duration seconds."
agc
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.