代码之家  ›  专栏  ›  技术社区  ›  Victor

我该怎么加速?

  •  2
  • Victor  · 技术社区  · 15 年前

    下面的代码列出了姓名和“数字”,并为每个人提供了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."
    

    我想用它列出100万个名字。很慢,我预料到;太慢了,我失去了耐心,尝试了10公里的名字。这也很慢,但几秒钟后就完成了。

    我生成这些名称的原因是对它们进行排序。令我惊讶的是,当我整理10公里名单时,它是即时的。

    怎么会这样?

    是不是有什么东西让事情变得异常缓慢?排序和生成都在访问文件,因此如何加快排序速度?我的随机数数学在列表生成器中是什么在减慢它?

    这是我的分类脚本。

    #!/bin/sh
    #first argument is list to be sorted, second is output file
    tr -s '' < $1 | sort -n -k2 > $2
    
    7 回复  |  直到 15 年前
        1
  •  5
  •   TheBonsai    15 年前

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

    这就是imho在优秀代码和高效代码之间的一个很好的中间方法(在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"
    

    两者的速度差不多。

    修复方法与所有其他修复方法相同:

    • 不要频繁关闭和重新打开 文件
    • 使用外壳算法
    • 啊,是的,用引号,但那是为了理智,而不是为了速度。
        2
  •  5
  •   John Kugelman Syzygies    15 年前
    for i in `seq 1 $n`
    

    伊克斯!这将生成1000000个参数 for 循环。那 seq 打电话要花很长时间, 长的 时间。尝试

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

    顺便说一下,注意到美元符号的缺失。特别地, var++ 语法要求您从变量名中省略美元符号。你也可以在其他地方使用或省略它们:它可以是 i <= n $i <= $n 任何一个。我认为,你应该完全忽略美元符号 let , declare for ((x; y; z)) 声明。参见 sh 完整解释的手册页。

        3
  •  5
  •   Community rcollyer    7 年前

    使用shell生成这样的随机数并不是它设计的目的。在另一种语言(如fortran、perl或c)中,编写代码以从统一分布中生成随机数可能会更好。

    在您的代码中,有一件事情非常慢,那就是从1..1e7生成一个数字序列,并将它们全部分配给一个变量。这很可能是浪费,但如果你想确定的话,你应该做个侧面介绍。AS 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秒,当您添加随机数生成时,脚本在同一台计算机上的生成时间超过3分钟。和你一样,我也很沮丧,我试着用剧本来加快速度。以下是我正在使用的代码的简短版本:

    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调用时,我没有注意到任何加速。

        4
  •  4
  •   John Nilsson    15 年前

    我想“>>$file”可能是您问题的根源。在我的系统上,您的脚本需要10秒钟来生成10000。如果我删除$file参数,只需使用stdout并将整个内容捕获到一个文件中,这需要一秒钟的时间。

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

    实0.07.552 用户0m1.355s 系统0M1.886S

    $time./gen2.sh 10000>n2.txt

    实0.806米 用户0.576 系统0.140秒

        5
  •  3
  •   chaos    15 年前

    不知道这是不是整个故事,但是重新打开文件附加到它的每个名字是没有帮助的。在任何可以保持打开的文件句柄写入的上下文中执行整个操作都会有很大帮助。

        6
  •  2
  •   Roger Pate    15 年前

    (我有种感觉,你可能不喜欢这个答案,但从技术上讲,你没有指定答案必须留在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
    
        7
  •  2
  •   pgs    15 年前

    在主循环中尝试此操作:

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

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

    我同意这里其他人的观点:这必须是巴什吗?

    编辑:添加混乱的建议以保持文件的打开,而不是为每个名称的附加打开。