我如何加快速度?

时间:2009-06-27 01:36:23

标签: bash list sorting scripting random

以下代码列出了名称和“数字”,并为每个人提供了15到90岁之间的随机年龄。

#!/bin/sh

file=$1
n=$2

# if number is zero exit
if [ "$n" -eq "0" ]
then
    exit 0
fi

echo "Generating list of $n people."

for i in `seq 1 $n`;
do
    let "NUM=($RANDOM%75)+15"
    echo "name$i $NUM (###)###-####" >> $file
done

echo "List generated."

有了它,我正在尝试列出1M名称。它很慢,我预料到了;它太慢了,我失去了耐心,尝试了10K的名字。这也很慢,但它在几秒钟内就完成了。

我生成名称的原因是对它们进行排序。令我惊讶的是,当我对10K名称列表进行排序时,它是即时的。

这怎么可能?

是否有什么事情会让这个变得缓慢?排序和生成都是访问文件,那么排序如何更快?列表生成器中的随机数数学是什么减慢了它?

这是我的排序脚本。

#!/bin/sh
#first argument is list to be sorted, second is output file
tr -s '' < $1 | sort -n -k2 > $2

7 个答案:

答案 0 :(得分:5)

使用shell生成这样的随机数并不是它的设计目的。你可能会更好地编写一些东西,用另一种语言(如Fortran,Perl或C)从统一分布中生成随机数。

在你的代码中,一件非常慢的事情是从1..1e7生成一系列数字并将它们全部分配给变量。这可能非常浪费,但如果你想确定,你应该描述一下。正如chaos指出的那样,附加到文件也可能非常昂贵!

在Python中,您可以执行以下操作:

#!/usr/bin/python
import random
count = 1

print ' '.join( ['name', 'age'] )
while count <= 1000000:
    age = random.randrange(15,90)
    count = count + 1
    name = 'name' + str(count)
    print ' '.join( [ name, str(age) ] )

在我的笔记本电脑上运行它需要大约10秒钟。将seq从1分配给1000000需要大约10秒,当您添加随机数生成时,您的脚本在同一台机器上花费超过三分钟。我和你一样感到沮丧,并且使用脚本来尝试让它更快。这是我正在使用的缩短版代码:

for x in `seq 1 10000`; do
   let "NUM=($RANDOM%75)+15"
   echo $NUM >> test.txt
done

运行此操作需要大约5.3秒:

$ time ./test.sh
real    0m5.318s
user    0m1.305s
sys     0m0.675s

删除文件追加并简单地将STDOUT重定向到单个文件,提供以下脚本:

for x in `seq 1 10000`; do
   let "NUM=($RANDOM%75)+15"
   echo $NUM
done

运行此约需半秒钟:

$ time ./test.sh > test.txt
real    0m0.516s
user    0m0.449s
sys     0m0.067s

程序的缓慢至少部分是由于附加到该文件。奇怪的是,当我尝试用for循环交换seq调用时,我没有注意到任何加速。

答案 1 :(得分:5)

for i in `seq 1 $n`

糟糕!这会为for循环生成1,000,000个参数。 seq电话会花费很长很长的时间。尝试

for ((i = 1; i <= n; i++))

顺便提一下,请注意缺少美元符号。特别是,var++语法要求您从变量名中省略美元符号。您也可以在其他地方使用或省略它们:它可以是i <= n$i <= $n,也可以是其中之一。我的方式,你应该完全在letdeclarefor ((x; y; z))语句中省略美元符号。有关完整说明,请参阅sh手册页的ARITHMETIC EVALUATION部分。

答案 2 :(得分:5)

不是新答案,只是新代码。

这就是恕我直言在优秀和高效的代码之间的良好中间路径(在Bash中效率很高,它很慢,它是一个shell ...)

for ((i=1;i<=n;i++));
do
  echo "name$i $((NUM=(RANDOM%75)+15)) (###)###-####"
done > "$file"

替代方案,不使用经典的计数器循环

i=1
while ((i<=n)); do
  echo "name$((i++)) $((NUM=(RANDOM%75)+15)) (###)###-####"
done > "$file"

两者速度大致相同。

修正与所有其他修正案相同:

  • 不要经常关闭并重新打开 文件
  • 使用shell算法
  • 啊是的,并使用QUOTES,但这是为了理智,而不是速度

答案 3 :(得分:4)

我想'&gt;&gt; $ file'可能是您问题的根源。在我的系统上,你的脚本需要10秒才能生成10000.如果我删除了$ file参数,而只是使用stdout并将整个内容捕获到一个文件,它需要一秒钟。

$ time ./gen1.sh n1.txt 10000 生成10000人的列表。 列表生成。

真正的0m7.552s 用户0m1.355s sys 0m1.886s

$ time ./gen2.sh 10000&gt; n2.txt

真实0m0.806s 用户0m0.576s sys 0m0.140s

答案 4 :(得分:3)

不知道这是不是整个故事,但重新打开文件以附加到它的每个名字都无济于事。在任何可以保持打开文件句柄写入的上下文中完成所有事情应该会有很大帮助。

答案 5 :(得分:2)

试试这个主循环:

seq 1 $n | while read i
do
    let "NUM=($RANDOM%75)+15"
    echo "name$i $NUM (###)###-####"
done > $file

这将使seq和循环并行工作,而不是在开始循环之前等待seq完成。这在多核/ CPU上会更快,但在单核上稍慢。

我同意其他人的观点:它必须是bash吗?

编辑:添加混乱'建议以保持文件打开,不打开以追加每个名称。

答案 6 :(得分:2)

(我有一种感觉你可能不喜欢这个答案,但你在技术上没有指明答案必须保留在bash中!:P)

在原型语言中快速开发一些东西是很常见的,然后可能根据需要切换到另一种语言(通常是C语言)。这是一个非常类似的Python程序供您比较:

#!/usr/bin/python
import sys
import random

def main(args=None):
    args = args or []
    if len(args) == 1:
        # default first parameter
        args = ["-"] + args
    if len(args) != 2:
        sys.stderr.write("error: invalid parameters\n")
        return 1
    n = int(args[1])
    output = sys.stdout if args[0] == "-" else open(args[0], "a")

    for i in xrange(1, n + 1):
        num = random.randint(0, 74)
        output.write("name%s %s (###)###-####\n" % (i, num))

    sys.stderr.write("List generated.\n") # see note below

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))

注意:仅使用stdout进行“实际输出”而不是状态通知允许此程序与其他程序并行运行,将数据直接从stdout传输到另一个stdin。 (可以使用* nix中的特殊文件,但如果可以使用stdout则更容易。)示例:

$./rand_names.py 1000000 | sort -n -k2 > output_file

它应该足够快:

$time ./rand_names.py 1000000 > /dev/null
List generated.

real    0m16.393s
user    0m15.108s
sys     0m0.171s