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

Edw*_*lds 32 bash python3

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

$ cat python.py
#!/bin/python
# python v3.5
i=0;
while i<=1000000000:
    i=i+1;
Run Code Online (Sandbox Code Playgroud)

重击代码:

$ cat bash2.sh
#!/bin/bash
# bash v4.3
i=0
while [[ $i -le 1000000000 ]]
do
let i++
done
Run Code Online (Sandbox Code Playgroud)

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

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

PSk*_*cik 55

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
Run Code Online (Sandbox Code Playgroud)

详情:

  • 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-unknown-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
Run Code Online (Sandbox Code Playgroud)

从结果来看:

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

  • kshfor (循环最快,每次迭代大约2.7µs
  • dashwhile [循环最快,每次迭代约5.8µs

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

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


同样,shell 的缓慢应该无关紧要,因为 shell 的典型模式是卸载到一些外部优化程序的进程。

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

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

time python3 -c ' '
Run Code Online (Sandbox Code Playgroud)

在我的 PC 上需要 30 到 40 毫秒,而 shell 需要大约 3 毫秒。如果你启动了很多脚本,这很快就会加起来,你可以在 python 启动所需的额外 27-37 毫秒内完成非常多的工作。小脚本可以在那个时间范围内完成多次。

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

  • 我想也许 gcc 优化器完全消除了循环。不是,但它仍然在做一个有趣的优化:它使用 SIMD 指令并行执行 4 次加法,将循环迭代次数减少到 250000 次。 (2认同)

Wil*_*ard 20

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

BUGS
       It's too big and too slow.
Run Code Online (Sandbox Code Playgroud)

;)


关于 shell 脚本和其他编程语言之间概念差异的优秀入门读物,我强烈推荐阅读:

最相关的摘录:

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

...

IOW,在 shell 中,尤其是处理文本时,您调用尽可能少的实用程序并让它们协作完成任务,而不是依次运行数千个工具,等待每个工具启动、运行、清理,然后再运行下一个。

...

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

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


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


小智 18

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

测试 1:18.233 秒

#!/bin/bash
i=0
while [[ $i -le 4000000 ]]
do
    let i++
done
Run Code Online (Sandbox Code Playgroud)

测试2:20.45s

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

测试3:17.64s

#!/bin/bash
i=0
while [[ $i -le 4000000 ]]; do let i++; done
Run Code Online (Sandbox Code Playgroud)

测试4:26.69s

#!/bin/bash
i=0
while [ $i -le 4000000 ]; do let i++; done
Run Code Online (Sandbox Code Playgroud)

测试5:12.79s

#!/bin/bash
export LC_ALL=C

for ((i=0; i != 4000000; i++)) { 
:
}
Run Code Online (Sandbox Code Playgroud)

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

  • +1 对于 LC_ALL 建议,我不知道。 (3认同)

Sté*_*las 10

如果您将 shell 用于它的设计目的,那么它就是高效的(尽管效率很少是您在 shell 中寻找的)。

shell 是一个命令行解释器,它旨在运行命令并让它们协作完成任务。

如果你想计数到 1000000000,你调用一个(一个)命令来计数,比如seq, bc,awkpython/ perl... 运行 1000000000 个[[...]]命令和 1000000000 个let命令肯定会非常低效,尤其是bash它是所有 shell 中最慢的。

在这方面,shell 会快很多:

$ time sh -c 'seq 100000000' > /dev/null
sh -c 'seq 100000000' > /dev/null  0.77s user 0.03s system 99% cpu 0.805 total
$ time python -c 'i=0
> while i <= 100000000: i=i+1'
python -c 'i=0 while i <= 100000000: i=i+1'  12.12s user 0.00s system 99% cpu 12.127 total
Run Code Online (Sandbox Code Playgroud)

当然,大部分工作都是由 shell 调用的命令完成的,这是应该的。

现在,你当然可以用以下方法做同样的事情python

python -c '
import os
os.dup2(os.open("/dev/null", os.O_WRONLY), 1);
os.execlp("seq", "seq", "100000000")'
Run Code Online (Sandbox Code Playgroud)

但这并不是你做事的方式,python因为python它主要是一种编程语言,而不是命令行解释器。

请注意,您可以这样做:

python -c 'import os; os.system("seq 100000000 > /dev/null")'
Run Code Online (Sandbox Code Playgroud)

但是,python实际上会调用 shell 来解释该命令行!


ste*_*eve 7

答:Bash 比 Python 慢得多。

一个小例子是在博文中的几种语言的性能