【问题标题】:Better performance substitute for egrep更好的性能替代 egrep
【发布时间】:2021-03-09 09:58:57
【问题描述】:

我正在解析一个 18GB 文件 summarize_eigenvectors.out 中的信息,该文件具有以下结构:

 Special analysis for state  3293   3.56009
    c    v    weight        ik        kx        ky        kz
    1    1   0.00000         1   0.00000   0.00000   0.00000
    1    1   0.00000         2   0.00000   0.04167   0.00000
    1    2   0.00000         1   0.00000   0.00000   0.00000
    1    2   0.00000         2   0.00000   0.04167   0.00000
    2    1   0.00000         1   0.00000   0.00000   0.00000
    2    1   0.00000         2   0.00000   0.04167   0.00000
    2    2   0.00000         1   0.00000   0.00000   0.00000
    2    2   0.00000         2   0.00000   0.04167   0.00000

Special analysis for state  3294   3.56013
    c    v    weight        ik        kx        ky        kz
    1    1   0.00000         1   0.00000   0.00000   0.00000
    1    1   0.00000         2   0.00000   0.04167   0.00000
    1    2   0.00000         1   0.00000   0.00000   0.00000
    1    2   0.00000         2   0.00000   0.04167   0.00000
    2    1   0.00000         1   0.00000   0.00000   0.00000
    2    1   0.00000         2   0.00000   0.04167   0.00000
    2    2   0.00000         1   0.00000   0.00000   0.00000
    2    2   0.00000         2   0.00000   0.04167   0.00000

在实际系统中,索引上升到

    12  12   0.00000      1152   0.00000   0.00000   0.00000

我正在使用 egrep 将大文件的每个部分解析为较小的文件。另一个文件 summarize_eigenvectors_range.in 包含以下内容:

1870        #total number of excitons to analyze
0.35600872E+01
0.35601277E+01
0.35603700E+01
....

主要脚本如下:

#!/bin/bash

P=`pwd`

#if [ -d summarize_eigenvectors ]; then
#   rm -r summarize_eigenvectors
#   mkdir summarize_eigenvectors
#   cd summarize_eigenvectors
#else
#   mkdir summarize_eigenvectors
    cd summarize_eigenvectors
#fi

number=$(awk 'NR==1''{ print$1 }' ../summarize_eigenvectors_range.in)
line=$(( $number + 1 ))
i=2
#start_id=$(grep -m 1 "Special analysis for state" ../summarize_eigenvectors.out | awk '{ print$5 }')
start_id=4137
echo start_id = $start_id

while [ $i -le $line ]
do
    exciton_n=$(awk -v i="$i" 'NR==i''{ print$1 }' ../summarize_eigenvectors_range.in)
    nstring=$(echo $exciton_n | awk -F"E" 'BEGIN{OFMT="%10.5f"} {print $1 * (10 ^ $2)}')
    nid=$(( $start_id + $i - 2 ))
    name=`echo "$nid"_"$nstring" | sed -e 's/[[:space:]]//g'`
    echo "$name"
    mkdir "$name"
    cd "$name"
    mkdir sorted
    egrep -A 165889 "Special analysis for state.*$nid" ../../summarize_eigenvectors.out > $name.txt
    for c in $(seq 1 12); do
        for v in $(seq 1 12); do
            echo -e "    c    v    weight        ik        kx        ky        kz" > "$name"-"$c"_"$v".txt
            awk -v c="$c" -v v="$v" '{ if ($1 == c && $2 == v)  print }' $name.txt >> "$name"-"$c"_"$v".txt
            cat "$name"-"$c"_"$v".txt | sort -k 3 -g -r > ./sorted/"$name"-"$c"_"$v"-sorted.txt
        done
    done
    cd ..
    i=$(( $i + 1 ))
done

此操作每个部分大约需要 30 秒,而我有数千个这样的部分。有没有更好的方法可以让脚本运行得更快?我正在考虑使用awk,但不知道如何将字符串和变量的搜索结合在一起;也不知道会不会有更好的表现。

关于性能瓶颈在哪里的任何见解以及关于如何改进代码的任何建议?

示例输出: 几千个文件,一种类型包含“特别分析”部分中的所有内容,内容如下:

 Special analysis for state  {nid}   x.xxxxx
    c    v    weight        ik        kx        ky        kz
    1    1   0.00000         1   0.00000   0.00000   0.00000
....
    12  12   0.00000      1152   0.00000   0.00000   0.00000

另一种将上述文件分为c1v1 c1v2等c1v1文件如下所示

    c    v    weight        ik        kx        ky        kz
    1    1   0.00000         1   0.00000   0.00000   0.00000
    1    1   0.00000         2   0.00000   0.00000   0.00000
....
    1    1   0.00000      1152   0.00000   0.00000   0.00000

c1v2 文件如下所示

    c    v    weight        ik        kx        ky        kz
    2    2   0.00000         1   0.00000   0.00000   0.00000
    2    2   0.00000         2   0.00000   0.00000   0.00000
....
    2    2   0.00000      1152   0.00000   0.00000   0.00000

【问题讨论】:

  • 请发布具有预期输出的可测试样本。
  • 需要更多详细信息,例如,$line 是什么,对您正在做的事情的简要文字说明(将 1154 行的块解析为单独的文件?),也许还有一些示例输入行前 2 个部分,以及与样本输入对应的所需输出;话虽如此,我猜整个事情可以用awk 和单次遍历文件来完成(而不是你目前通过文件进行的数千次遍历)
  • @Jacek :我看不到您在脚本中设置 line 的位置,但无论如何,您为循环中的每个迭代创建一个子进程,并且这些子进程中的每一个都有从头到尾依次遍历大文件。也许您可以考虑重新设计您的文章,以便它只需要一次文件传递。您只需要一个“缓冲区”来保存比赛前后的 1153 行,以创建不同的输出文件。
  • 当我看到有人知道使用 $(...) 但只对某些行使用的代码时,我总是感到困惑,例如start_id=$(...)name=`...` 仅在 5 行之后。

标签: bash awk grep


【解决方案1】:

如果您的目标只是split the file on a delimiter,那么您应该使用csplit(如果可用)。 像这样的:

csplit --quiet --digits=4 -z the_18gb_file.out "/Special analysis/" "{*}" 

【讨论】:

  • 这对生成第一类文件很好,但是后续操作会很麻烦。
【解决方案2】:

性能不佳不是因为egrep,而是因为您在while循环中逐行读取文件(使用grep时甚至不需要)。但是,如果您想拆分文件并在剩余部分上做一些grep,您可能会按照DavidW 的建议寻找一种不使用while 循环来拆分文件的方法。

【讨论】:

    【解决方案3】:

    正如其他人所指出的,真正的问题是您一遍又一遍地反复遍历整个 18Gb 输入文件。您甚至已经在使用 Awk,因此转换为单通道不会特别困难。

    awk '/Special analysis for state / {
        if(out) close(out)
        out = $5 "_some_other_identifier_taken_from_another_file-1_1.txt"
        n = 1153 }
    n { n--; print >out }' the_18gb_file.out
    

    这假设分析在输入文件中没有重叠。

    您没有透露“其他标识符”应该来自哪里,但希望它不会很难集成到这个脚本中。

    Awk 一次检查一行并处理每一行的脚本;未设置的变量将简单地为空(在布尔上下文中方便地计算为“假”,在数字上下文中计算为零)。当我们在正在处理的行中看到新条目的标记时,我们关闭任何已经打开的文件(如果 out 是从前一次迭代中定义的),然后为下一个文件名设置计数器以及要写入的行数。如果我们还没有写那么多行,则下一个条件为真;然后我们将n递减并写入我们在之前条件中指定的文件。

    【讨论】:

    • 这是一个常见的问题;不幸的是,很少有人会想到在谷歌上搜索明显的“我的算法很烂,但我肆无忌惮地责怪我的工具”。
    • 你能解释一下这段代码吗?我不明白 if 部分。这似乎是一条前进的道路。
    • 添加了简要说明。如果您需要帮助嫁接“其他标识符”,也许可以接受并提出一个新问题;如果您愿意,请随时在此处联系我并发表评论。
    • 感谢您的解释。但是,您知道这与 csplit 尝试相比如何吗?
    • 可能它们大致相同,但如果在文件顶部或我们提取的条目之间有一些您不想要的行,那么这个更接近您的规范。
    【解决方案4】:

    非常感谢您的所有意见。我已经采纳了 DavidW 的建议,即在初始步骤中使用 csplit。问题中的 awk 行完成的其余归档仍然需要工作。但是,我已经设法使用下面附加的代码并行化该过程。现在,最初的 14 小时工作已压缩为在几分钟内完成。希望对遇到相同问题的人有所帮助。

    #!/bin/bash
    
    P=`pwd`
    
    if [ -d summarize_eigenvectors ]; then
        rm -r summarize_eigenvectors
        mkdir summarize_eigenvectors
        cd summarize_eigenvectors
    else
        mkdir summarize_eigenvectors
        cd summarize_eigenvectors
    fi
    
    csplit --quiet --digits=4 -z ../summarize_eigenvectors.out "/Special analysis/" "{*}" 
    
    mv xx0000 k-point_index
    
    task(){
        nid=$(awk 'NR==1''{ print$5 }' $file)
        energy=$(awk 'NR==1''{ print$6 }' $file)
        name=$(echo "$nid"_"$energy")
        echo $name
        mkdir $name
        mv $file ./$name/$name.txt
        cd $name
    
        for c in $(seq 1 12); do
            for v in $(seq 1 12); do
                echo -e "    c    v    weight        ik        kx        ky        kz" > "$name"-"$c"_"$v".txt
                awk -v c="$c" -v v="$v" '{ if ($1 == c && $2 == v)  print }' $name.txt >> "$name"-"$c"_"$v".txt
                cat "$name"-"$c"_"$v".txt | sort -k 3 -g -r > "$name"-"$c"_"$v"-sorted.txt &
            done
        done
        cd ..
    }
    
    
    for file in ./xx*; do
    ((i=i%120)); ((i++==0)) && wait
    task "$file" &
    done
    wait
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-11-11
    • 1970-01-01
    • 2013-06-03
    • 1970-01-01
    • 2017-02-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多