为什么我的程序运行这么长时间?

时间:2016-07-14 21:53:08

标签: bash

我已经在bash中编写了一个小程序,它将多个(选项卡)行(六列)转换为一行,其中包含12列,如下所示:

    $1          $2      $3          $4          $5  $6      $7      $8      $9  $10     $11     $12
input
Scaffold952 345718  345781  aug3.g8795.t1   .   -
Scaffold952 346504  346534  aug3.g8795.t1   .   -
Scaffold952 346721  346733  aug3.g8795.t1   .   -
Scaffold952 348148  348241  aug3.g8795.t1   .   -
output
Scaffold952 345718  345781  aug3.g8795.t1   .   -   345718  345718  0   4   63,30,12,93 0,786,1003,2430

要完成此操作:

  1. 取六列输入,按名称排序(第4列),然后按开始(第2列)
  2. 排序
  3. 从输入中生成名称列表并删除重复项
  4. 在步骤2中生成的foreach行
    • 在输入中找到相应的行
    • 使用第二列和第三列构建第11列和第12列,将值插入数组
    • 将列和数组组合成最终的12列输出
  5. 插入输出文件
  6. 第11栏由$ 3 - $ 2组成;对于每6个列行,第12列由$ 2减去第一个$ 2值(345718)组成,并打印为csv。

    我的代码:

    #!/bin/bash
    
    input=$1
    output=$2
    > $output
    # functions
    function joinArray { local IFS="$1"; shift; echo "$*"; }
    # sort input
    sort -k4,4 -k2,2 -o $input < $input
    
        awk '{ print $4 }' $input | uniq | while read -r line; do
            dup="$(grep -c $line $input)"
            start="$(grep $line $input | awk 'NR==1 { print $2 }')"
            records="$(grep $line $input | awk 'NR==1 { print $0 }')"
    
            grep $line $input | {
            while read -r record; do
                blocksize+=($(awk '{ print $3 - $2 }' <<< "$record"))
                blockstart+=($(awk -v var="$start" '{ print $2 - var }' <<< "$record"))
            done
    #       combine input with arrays to form 12 col output
            bed12[0]+=$(awk '{ print $1 }' <<< "$records")
            bed12[1]+=$(awk '{ print $2 }' <<< "$records")
            bed12[2]+=$(awk '{ print $3 }' <<< "$records")
            bed12[3]+=$(awk '{ print $4 }' <<< "$records")
            bed12[4]+=$(awk '{ print $5 }' <<< "$records")
            bed12[5]+=$(awk '{ print $6 }' <<< "$records")
            bed12[6]+=$(awk '{ print $2 }' <<< "$records")
            bed12[7]+=$(awk '{ print $2 }' <<< "$records")
            bed12[8]+='0'
            bed12[9]+=$dup
            bed12[10]+=$(joinArray $',' "${blocksize[@]}")
            bed12[11]+=$(joinArray $',' "${blockstart[@]}")
    
            joinArray $'\t' "${bed12[@]}" >> $output
            }
        done
    

    到目前为止,我一直无法有效地运行此代码,我希望改进它,就像标准大小的文件(~30,000行)一样,需要花费三个小时才能完成。我不确定导致问题的原因可能是每次写入记录时打开/关闭输出文件;嵌套while循环;数组?这是一个糟糕的程序,语言不是一个合适的选择,或者这是一个这么大的文件(1.7 MB)?

1 个答案:

答案 0 :(得分:3)

要记住的一些事情:

  • 您放入while read循环内的所有内容都会为每个处理过的行运行一次。
  • 运行$(...)执行fork()调用,创建一个全新的进程树条目,在该新进程中运行您附带的代码,读取其标准输出,并等待该进程完成。这是开销的很多
  • 每次运行awk - 虽然它是一个非常快速的语言解释器 - 或grep,你实际上是在启动一个新程序:分叉它,动态加载它的库依赖关系,接线它的stdin和stdout ......这也是很多开销。
  • 每次执行重定向时,例如>>foo打开输出文件以查找包含重定向的命令,然后在单个文件完成后再次关闭它。 (对于shell语言尤其如此; awk缓存和重用文件描述符的一些>实现,尤其包括GNU一个。)
  • 不幸的是,在撰写本文时,<<<的bash实现涉及在磁盘上创建临时文件。如果在/dev/fd是受支持的接口的平台上的紧密内循环中使用此构造,< <(printf '%s' "$foo")因此可能比<<<"$foo"更快 - 尽管我不建议实际执行此操作练习除非你需要它,因为bash的未来版本希望能够解决这种行为并用更容易阅读的语法做正确的事。

那么,你能做什么?

  • 对于awk之类的工具,只有当您可以在多行输入中重复使用一个长时间运行的实例时才使用它们。

    坦率地说,这是这里最重要的建议。将所有工作从bash移到单个awk调用中,您将完成。

  • 要将输入流解析为多个字段,请使用bash自己的内置函数:

    read -r first_field second_field third_field ...
    
  • 在一个封闭的外部范围内进行重定向,使用exec >foo重定向整个程序的stdout(或整个子shell,如果在这样的上下文中执行),或者将>foo放在后面done关闭循环以在该循环的持续时间内重定向stdout。