我的脚本有问题还是 Bash 比 Python 慢很多?

我的脚本有问题还是 Bash 比 Python 慢很多?

我通过运行 10 亿次循环来测试 Bash 和 Python 的速度。

$ cat python.py
#!/bin/python
# python v3.5
i=0;
while i<=1000000000:
    i=i+1;

重击代码:

$ cat bash2.sh
#!/bin/bash
# bash v4.3
i=0
while [[ $i -le 1000000000 ]]
do
let i++
done

使用该time命令,我发现 Python 代码只需要 48 秒即可完成,而 Bash 代码则需要 1 个多小时才能杀死脚本。

为什么会这样呢?我预计 Bash 会更快。我的脚本有问题吗?还是 Bash 使用这个脚本真的慢很多?

答案1

Shell 循环很慢,bash 的循环最慢。 Shell 不适合在循环中执行繁重的工作。 Shell 旨在针对批量数据启动一些外部优化进程。


不管怎样,我很好奇 shell 循环如何比较,所以我做了一些基准测试:

#!/bin/bash

export IT=$((10**6))

echo POSIX:
for sh in dash bash ksh zsh; do
    TIMEFORMAT="%RR %UU %SS $sh"
    time $sh -c 'i=0; while [ "$IT" -gt "$i" ]; do i=$((i+1)); done'
done


echo C-LIKE:
for sh in bash ksh zsh; do
    TIMEFORMAT="%RR %UU %SS $sh"
    time $sh -c 'for ((i=0;i<IT;i++)); do :; done'
done

G=$((10**9))
TIMEFORMAT="%RR %UU %SS 1000*C"
echo 'int main(){ int i,sum; for(i=0;i<IT;i++) sum+=i; printf("%d\n", sum); return 0; }' |
   gcc -include stdio.h -O3 -x c -DIT=$G - 
time ./a.out

( 细节:

  • CPU:Intel(R) Core(TM) i5 CPU M 430 @ 2.27GHz
  • ksh:sh 版本(AT&T 研究)93u+ 2012-08-01
  • bash:GNU bash,版本 4.3.11(1)-release (x86_64-pc-linux-gnu)
  • zsh:zsh 5.2(x86_64-未知-linux-gnu)
  • 破折号:0.5.7-4ubuntu1

(缩写)结果(每次迭代的时间)是:

POSIX:
5.8 µs  dash
8.5 µs ksh
14.6 µs zsh
22.6 µs bash

C-LIKE:
2.7 µs ksh
5.8 µs zsh
11.7 µs bash

C:
0.4 ns C

从结果来看:

如果您想要稍微快一点的 shell 循环,那么如果您有语法[[并且想要快速的 shell 循环,那么您就处于高级 shell 中,并且也有类似 C 的 for 循环。然后使用 C 语言的 for 循环。它们的速度大约是while [同一 shell 中 -loops 的 2 倍。

  • 克什最快的for (循环大约为2.7微秒每次迭代
  • 短跑最快的while [循环大约为5.8微秒每次迭代

C for 循环可以快 3-4 个小数数量级。 (我听说 Torvalds 喜欢 C)。

优化后的 C for 循环比 bash 的while [循环(最慢的 shell 循环)快 56500 倍,比 ksh 的for (循环(最快的 shell 循环)快 6750 倍。


同样,shell 的速度慢应该没什么影响,因为 shell 的典型模式是将负载卸载到外部优化程序的几个进程。

通过这种模式,shell 通常可以更轻松地编写性能优于 python 脚本的脚本(我上次检查时,在 python 中创建流程管道相当笨拙)。

另一件需要考虑的事情是启动时间。

time python3 -c ' '

在我的 PC 上需要 30 到 40 毫秒,而 shell 大约需要 3 毫秒。如果您启动大量脚本,这个时间很快就会增加,并且您可以在 python 启动所需的额外 27-37 毫秒内完成非常多的事情。小脚本可以在这段时间内完成多次。

(NodeJs 可能是这个部门中最差的脚本运行时,因为它只需要大约 100 毫秒才能启动(即使一旦启动,您就很难在脚本语言中找到更好的性能))。

答案2

这是 bash 中的一个已知错误;请参阅手册页并搜索“BUGS”:

BUGS
       It's too big and too slow.

;)


要了解 shell 脚本与其他编程语言之间概念差异的优秀入门知识,我强烈建议阅读:

最相关的摘录:

shell 是一种高级语言。有人可能会说它甚至不是一种语言。它们位于所有命令行解释器之前。这项工作是由您运行的命令完成的,shell 只是为了编排它们。

...

IOW,在 shell 中,特别是在处理文本时,您调用尽可能少的实用程序并让它们配合任务,而不是按顺序运行数千个工具,等待每个工具启动、运行、清理,然后再运行下一个工具。

...

如前所述,运行一个命令是有成本的。如果该命令不是内置的,则成本巨大,但即使它们是内置的,成本也很大。

shell 并不是被设计成这样运行的,它们没有自称是高性能的编程语言。它们不是,它们只是命令行解释器。因此,在这方面几乎没有进行任何优化。


不要在 shell 脚本中使用大循环。

答案3

我做了一些测试,并在我的系统上运行了以下代码——没有一个能够达到具有竞争力所需的数量级加速,但你可以让它更快:

测试1:18.233s

#!/bin/bash
i=0
while [[ $i -le 4000000 ]]
do
    let i++
done

测试2:20.45秒

#!/bin/bash
i=0
while [[ $i -le 4000000 ]]
do 
    i=$(($i+1))
done

测试3:17.64s

#!/bin/bash
i=0
while [[ $i -le 4000000 ]]; do let i++; done

测试4:26.69s

#!/bin/bash
i=0
while [ $i -le 4000000 ]; do let i++; done

测试5:12.79s

#!/bin/bash
export LC_ALL=C

for ((i=0; i != 4000000; i++)) { 
:
}

最后一项中的重要部分是导出 LC_ALL=C。我发现如果使用它,许多 bash 操作最终会显着加快,特别是任何正则表达式函数。它还显示了使用 {} 和 : 作为无操作的未记录的语法。

答案4

答案:Bash 比 Python 慢得多。

博客文章中有一个小例子多种语言的性能

相关内容