在Bash中高效地计算数百万次浮点运算

6

背景

我在一家研究计算风暴潮的研究机构工作,试图使用Bash自动化一些HPC命令。目前的流程是,我们从NOAA下载数据并手动创建命令文件,逐行输入每个文件的位置以及程序从该文件读取数据的时间和风扩大系数。每次NOAA发布新的风暴进展情况时,每个下载中都会有数百个这些数据文件。这意味着我们在风暴期间花费了大量时间来制作这些命令文件。

问题

由于我只有一个用户帐户和每月限定的超级计算机使用时间,所以我使用自动化工具的选择受到了限制;我没有权限在这些计算机上安装新软件。此外,其中一些计算机是Cray,一些是IBM,一些是HP等等。它们之间没有一致的操作系统,唯一相似之处就是它们都是基于Unix的。因此,我可以使用Bash、Perl、awk和Python等工具,但不一定有csh、ksh、zsh、bc等工具:

$ bc
-bash: bc: command not found

此外,我的首席科学家要求我为他编写的所有代码都是Bash语言,因为他理解它,并且尽可能少地调用无法使用Bash完成的外部程序。例如,Bash不能执行浮点数算术运算,而我需要能够添加浮点数。我可以在Bash中调用Perl,但速度较慢。
$ time perl -E 'printf("%.2f", 360.00 + 0.25)'
360.25
real    0m0.052s
user    0m0.015s
sys     0m0.015s

1/20秒似乎不算长的时间,但当我需要在一个单一文件中进行100次调用时,这相当于处理一个文件约需要5秒钟。如果我们每6个小时才制作一个这样的文件,则并不太糟糕。但是,如果将这项工作抽象成更大的任务,例如一次向大西洋盆地指定1,000场合成风暴,以研究如果风暴更强或采取不同的路径会发生什么,那么5秒钟很快就会增长到一个多小时,仅用于处理文本文件。当您按小时计费时,这就成为一个问题。
问题:有什么好方法可以加速吗?我目前在脚本中使用了这个“for”循环(需要5秒钟才能运行):
for FORECAST in $DIRNAME; do
    echo $HOURCOUNT"  "$WINDMAG"  "${FORECAST##*/} >> $FILENAME;
    HOURCOUNT=$(echo "$HOURCOUNT $INCREMENT" | awk '{printf "%.2f", $1 + $2}');
done

我知道用awk或Perl单个调用循环遍历数据文件比为目录中的每个文件分别调用这两种语言的速度快100倍以上,而且这些语言可以轻松地打开文件并写入其中,但我遇到的问题是如何在它们之间传递数据。我找到了很多关于这三种语言的资源(awk、Perl、Python),但是在将它们嵌入Bash脚本方面,我还没有找到太多信息。最接近的方法就是制作一个awk命令的外壳:

awk -v HOURCOUNT="$HOURCOUNT" -v INCREMENT="$INCREMENT" -v WINDMAG="$WINDMAG" -v DIRNAME="$DIRNAME" -v FILENAME="$FILENAME" 'BEGIN{ for (FORECAST in DIRNAME) do
    ...
}'

但我并不确定这是否是正确的语法,即使是正确的语法,也不知道这是否是最好的方法,或者是否会起作用。我已经撞了几天头,决定在继续之前先问问互联网。


7
如果你同时拥有Perl和Python,为什么不完全使用它们来编写脚本呢?你所看到的低效性来自于必须启动整个Perl解释器来执行一个语句。如果你有一个50-100行的Perl脚本,它将非常高效,因为启动和解析成本已经分摊了。 - Barmar
3
一种可能性是启动一个Perl协处理器。然后您可以将浮点表达式传递给它,它将返回结果。 - Barmar
1
当你按小时计费时,这会带来问题。我认为你可以向你的PI提出一个很好的商业案例,使用Perl或Python。 - Andrew Morton
1
@halfer,是的,PI是“项目负责人”,即项目中的主要科学家。 - Jonathan E. Landrum
1
我想知道你是否能够编写类似于bash的Perl脚本,以便您的PI能够理解它们。实际上,这可能比一个充斥着像perl -E 'printf("%.2f", 360.00 + 0.25)'这样的东西的bash脚本更容易理解。 - David K
显示剩余6条评论
3个回答

3
Bash非常强大,只要你拥有所需的能力。对于浮点数,你基本上有两个选择,即bc(至少在你展示的盒子上没有安装[有点难以置信])或calccalc-2.12.4.13.tar.bz2
无论是哪种软件包,都可以很好地与bash集成,并具有灵活和强大的浮点功能。由于他们偏爱bash,所以我建议您安装bccalc。(工作安全是一件好事)
如果您的上级同意使用perlpython中的任一种,则任何一种都可以。如果您从未在其中任何一种中进行过编程,则双方都需要学习曲线,pythonperl稍微复杂一些。如果您的上司能够阅读bash,那么将perl翻译成bash对他们来说比翻译python更容易消化。
根据您解释的情况,这是您拥有的选择的公正概述。无论您选择哪种语言,任务对您来说都不应该是那么令人畏惧的。如果您遇到问题,请随时回信。

是的,我想知道是否值得为每个框搜索“bc” - 这可能只是路径中掉落的东西? - halfer
我更喜欢用csh编写所有内容;那是我最熟悉的shell。我会询问是否可以在我测试的机器上安装bc。 - Jonathan E. Landrum
它通常被安装在 /usr/bin 目录下,因此除非您完全丢失了可执行路径,否则键入 bc 应该会起作用。如果由于某种奇怪的原因,bc 的权限出现问题,它将不会显示为可执行文件,但是在您的可执行路径上执行 ls -al 命令将能找到它。使用 set | grep ^PATH 命令检查您的路径,然后进行相应操作。 - David C. Rankin
1
通过我收集的时间数据,我成功说服我的PI让我用Python重写脚本。执行时间从大约5秒降至约三分之一秒。感谢你对我进行足够的推动,让我使用正确的工具完成这项工作。 - Jonathan E. Landrum

1

仅为执行单个加法运算而启动awk或其他命令,这永远不会是有效的。Bash无法处理浮点数,因此您需要转变视角。您说您只需要添加浮点数,并且我猜测这些浮点数代表以小时为单位的持续时间。因此,请改为使用秒。

for FORECAST in $DIRNAME; do
    printf "%d.%02d  %s  %s\n" >> $FILENAME \
        $((SECONDCOUNT / 3600)) \
        $(((SECONDCOUNT % 3600) * 100 / 3600)) \
        $WINDMAG \
        ${FORECAST##*/}

    SECONDCOUNT=$((SECONDCOUNT + $SECONDS_INCREMENT))
done

(printf 是标准的格式化输出函数,比 echo 更加美观)

编辑:抽象为一个函数,并附带一些演示代码:

function format_as_hours {
    local seconds=$1
    local hours=$((seconds / 3600))
    local fraction=$(((seconds % 3600) * 100 / 3600))
    printf '%d.%02d' $hours $fraction
}

# loop for 0 to 2 hours in 5 minute steps
for ((i = 0; i <= 7200; i += 300)); do
    format_as_hours $i
    printf "\n"
done

如果$((SECONDCOUNT / 3600))是分数,这不会造成问题吗? - Jonathan E. Landrum
Bash 会丢弃任何小数部分,就像 C 语言中的整数除法一样。 - pdw
那对我没有任何好处。我必须维护小数部分。 - Jonathan E. Landrum
1
我的例子难道没有证明它是有效的吗?基本上,我首先计算整数部分,然后在单独的计算中计算小数部分,最后使用printf以适当的格式打印它。 尽管我从您的其他评论中看到您已经用Python重写了程序。那肯定是技术上更好的解决方案。我只是想表明,如果不可避免,可以仅使用整数算术完成任务。 - pdw
1
是的,在阅读了你的编辑之后,我可以看出你一直是正确的。是我的错误。Bash 不是我的强项。 - Jonathan E. Landrum
显示剩余2条评论

-2
如果所有这些计算机都是Unix,并且预计执行浮点运算,则每台计算机必须有一些支持浮点运算的应用程序可用。因此,可以使用类似以下命令的复合命令: bc -l some-comp || dc some-comp || ... || perl some comp

或者甚至是 echo "$HOURCOUNT $INCREMENT" | awk '{printf "%.2f", $1 + $2}'。问题在于时间。我可以将 awk 的输出管道传输到 Bash,但这需要很长时间。 - Jonathan E. Landrum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接