【问题标题】:why is this bash code so slow in comparison to corresponding cpp code?与相应的 cpp 代码相比,为什么这个 bash 代码这么慢?
【发布时间】:2021-01-24 09:36:54
【问题描述】:

我是操作系统的新手,因为它是最近介绍给我们的。 我们有 bash 代码可以作为我们的单圈实验运行。 我的最后一项任务是编写一个程序来打印一个数字是否为素数。 我首先在 cpp 中对其进行了编码,因为我对 cpp 感到满意。然后在经过大量谷歌搜索后在 bash 中进行相同的编码。但是我的 bash 代码比 cpp 代码慢很多。

我写了一个 cpp 代码来检查一个数字是否是素数。 cpp代码:

#include <iostream>
using namespace std;

bool isprime(int a) {
    if (a == 2 || a == 3)
        return true;
    if (a % 2 == 0)
        return false;
    if (a % 3 == 0)
        return false;
    for (int i = 1; (6 * i - 1) * (6 * i - i) <= a; ++i) {
        if (a % (6 * i - 1) == 0)
            return false;
        if (a % (6 * i + 1) == 0)
            return false;
    }
    return true;
}

int main() {
    int n;
    cin >> n;
    cout << ((isprime(n)) ? "Prime" : "Not Prime") << endl;
    return 0;
}

我在 bash (linux) 中编写了相同的代码:

read num
f=1
if test $num -eq 2
  then f=1
elif test $num -eq 3
  then f=1
elif  test `expr $num % 2` -eq 0
  then f=0
elif test `expr $num % 3` -eq 0
  then f=0
else
  for (( i=1; $((`expr i\*6-1`))**2 <= num; i++))
  do
    t=`expr $i \* 6 - 1`
    if test `expr $num % $t` -eq 0
    then
      f=0
    fi
    t=`expr $i \* 6 + 1`
    if test `expr $num % $t` -eq 0
    then
      f=0
    fi
  done
fi

if test $f -eq 1
then
  echo 'Prime'
else
  echo 'Not-Prime'
fi

但是 cpp 代码非常快,而 bash 代码非常慢 这里是终端输入和输出

a@a:~/Cp$ cat > input
1000000007
^?^C
a@a:~/Cp$ time ./a.out < input
Prime

real    0m0.003s
user    0m0.003s
sys 0m0.000s
a@a:~/Cp$ time ./prog.sh <input
^C
real    0m8.258s
user    0m5.906s
sys 0m2.794s
a@a:~/Cp$ # I interrupted the execution.

我不知道为什么会这样?

【问题讨论】:

  • 不确定您在这里的期望,您将 C++(一种编译语言)与 Bash(一种使用大量子进程进行基本处理的解释性语言)进行比较。当然 C++ 会更快。
  • 请检查for循环,如果条件写得不好,它可能会在for循环中停留无限时间。我建议你单独检查一下情况。
  • 如果你不调用expr(子shell)并使用bash内部数学来代替,bash会变得更快。
  • @ArshdeepSingh :在 bash 版本的循环体内,您正在创建 4 个子进程(在每次迭代中!)。如果您从 C++ 中执行此操作,它也会很慢。
  • 不要使用反引号,而是使用$(..)。并且不要使用expr。曾经。忘记它的存在。只需if ((num % t == 0)); then 等。

标签: c++ bash performance performance-testing


【解决方案1】:

C++ 与其他编译语言一样,在解释语言(如 bash、sh、python)方面肯定具有更好的性能

这是因为当您使用编译程序时,只会启动一个进程,而当您运行解释程序时,它会实例化大量子进程,也用于基本操作。

c++ 程序比 bash 程序快是微不足道的。

【讨论】:

  • 谢谢你,我不知道。我很好奇,因为即使是 python 也能提供即时输出。而 bash 就像永远一样。
  • if is forever 可能for循环的条件写得不好。在说这个之前检查它。
  • 该代码适用于小输入。它提供了高达 N
  • 解释程序不一定会产生子进程。此外,不是 C++ 作为一种具有更好性能的语言,而是生成的可执行文件通常性能更好。 C++也可以解释
  • 谢谢@ZigRazor,我可以看到使用top,上面的代码没有使用太多内存,但由于你提到的原因很慢。有人告诉我用基本的数学语句检查 expr 命令,这是一个巨大的优化。现在代码在一秒钟内运行。对于 N ~= 10^10。
【解决方案2】:

除了测试和数学之外,代码没有太多内容,但您在这些单独的 bash 级命令上花费了大量时间。

我们可以通过将所有代码下推到单个 awk 调用中来消除 bash 级命令/进程调用的开销。

这是在 awk 中运行的 bash 代码的详细副本:

$ cat isprime
#!/usr/bin/bash
input=${1}                                                 # user's input number;
                                                           # OP will probably want to add some logic to validate the input as numeric

awk -v num="${input}" '                                    # pass input number to awk in 'num' variable
BEGIN { f=1 }                                              # init our flag
      {        if (  num      == 2 ) { f=1 }
          else if (  num      == 3 ) { f=1 }
          else if ( (num % 2) == 0 ) { f=0 }
          else if ( (num % 3) == 0 ) { f=0 }
          else for ( i=1 ; (i*6-1)**2 <= num ; i++ ) {
                   t=(i*6-1)
                   if ( (num % t) == 0 ) { f=0 ; break }   # for non-prime numbers we can abort the for loop to save some cycles
                   t=(i*6+1)
                   if ( (num % t) == 0 ) { f=0 ; break }   # for non-prime numbers we can abort the for loop to save some cycles
               }
      }
END { msg="Prime"
      if ( f == 0 ) { msg="Not-Prime" }
      print msg                                            # print our finding to stdout
    }
' <<< ""                                                   # awk needs a 'file' to work on so we'll feed it an empty here-string

为样本编号“1000000007”计时此脚本给我们:

$ time ./isprime 1000000007
Prime

real    0m0.262s
user    0m0.061s
sys     0m0.170s

注意:上述测试的重复运行显示实际运行时间从 0m0.082s0m0.262s;范围广泛的数字是由于我在 Windows7 VM 中的cygwin 下运行上述内容;我希望在 linux 环境中运行时间更一致(向低端?); ymmv ...

还有一个非质数测试:

$ time ./isprime 1000000006
Not-Prime

real    0m0.125s
user    0m0.015s
sys     0m0.045s

不如 C++ 程序快,但绝对比处理所有那些单独的 bash 级测试/进程调用快一点。

可以添加一些awkisms 以节省一些额外的运行时间,但为了回答这个问题,我希望尽可能轻松地将代码与 OPs C++/shell 代码进行比较sn-ps。


这个awk 解决方案的有用性将取决于输入(数字)的大小。

针对某些20-24 digit primes 测试上述内容表明此awk 解决方案立即放弃%2==0%3==0 测试。这归结为基本 awk 实现中的数值限制。

来自我的 awk 手册页:

-M
--bignum
    Force arbitrary precision arithmetic on numbers. This option has no effect if gawk is  not  compiled  to
    use the GNU MPFR and GMP libraries.  (In such a case, gawk issues a warning.)

-M 标志添加到上述awk 解决方案确实允许脚本在21 位​​素数上工作,但速度并不快......

awk -M -v num="${input}" ' ...

# and then:

$ time ./isprime 998887766553300224411
... as I type this it's going on 8 minutess of 100% cpu utilization (total of a single core)
... aborted after 13 minutes

虽然我毫不怀疑这个awk 解决方案最终会完成,但基本事实是它需要很长时间才能从1 计数到21-digit number,增量为1 (i++) (无论是bashawkC++ 等任何实现都是如此)。

对于非常大的数字,我会寻找其他(更复杂的)算法来验证数字是素数。

【讨论】:

  • 在 awk 中重写整个内容是一项相当大的工作。再加上额外的分析,我会要求每个人都支持这个答案!
  • 它在 82 毫秒内不工作,它在 17 毫秒内工作。我很震惊谢谢你。我有很多东西要学。
  • @ArshdeepSingh 很高兴听到;我知道我的 cygwin/VM 环境很慢,但没有意识到它很慢! :-)
  • 如果我们发现一个非质数(即我们设置了f=0),则在for循环之外更新为break,因为没有理由继续测试额外的i值点
【解决方案3】:

这是一个更快的 bash 版本

read num
f=1
if test $num -eq 2 -o $num -eq 3
  then f=1
elif  (( num % 2 == 0 || num % 3 == 0 ))
  then f=0
else
  for (( i=1; ; i++)); do
    t=$((i * 6 - 1))
    (( t * t > num )) && break
    (( num % t == 0 || num % (i * 6 + 1) == 0 )) && { f=0; break; }
  done
fi

if test $f -eq 1; then
  echo 'Prime'
else
  echo 'Not-Prime'
fi

所有对expr 的调用均已删除。

time bash test.sh <<< 1000000007
Prime

real    0m0.128s
user    0m0.000s
sys     0m0.094s

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-01-10
    • 2011-08-31
    • 1970-01-01
    • 2013-02-15
    • 1970-01-01
    • 2018-11-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多