Performance 优化IO密集型shell脚本的性能

Performance 优化IO密集型shell脚本的性能,performance,bash,scripting,Performance,Bash,Scripting,下面的bash代码逐行读取单个输入文件,并写入大量(~100)输出文件,表现出不合理的性能——在我希望它能够在数百万或数十亿行输入的范围内使用时,性能为10000行30秒 在下面的代码中,batches是一个已经定义的关联数组(在其他语言中是一个映射) 如何改进这一点 while IFS='' read -r line do x=`echo "$line" | cut -d" " -f1`; y=`echo "$line" | cut -d" " -f2`; # ec

下面的bash代码逐行读取单个输入文件,并写入大量(~100)输出文件,表现出不合理的性能——在我希望它能够在数百万或数十亿行输入的范围内使用时,性能为10000行30秒

在下面的代码中,
batches
是一个已经定义的关联数组(在其他语言中是一个映射)

如何改进这一点

while IFS='' read -r line
do
    x=`echo "$line" | cut -d"   " -f1`;
    y=`echo "$line" | cut -d"   " -f2`;
#   echo "find match between $x and $y";
    a="${batches["$x"]}";
    b="${batches["$y"]}";
    if [ -z $a ] && [ -n $b ]
        then echo "$line" >> Output/batch_$b.txt;
    elif [ -n $a ] && [ -z $b ]
        then echo "$line" >> Output/batch_$a.txt;
    elif [ -z $a ] && [ -z $b ]
            then echo "$line" >> Output/batch_0.txt;
    elif [ $a -gt $b ]
        then echo "$line" >> Output/batch_$a.txt;
    elif [ $a -le $b ]
            then echo "$line" >> Output/batch_$b.txt;
    fi

done < input.txt
而IFS=''读取-r行
做
x=`echo“$line”| cut-d”“-f1`;
y=`echo“$line”| cut-d”“-f2`;
#echo“查找$x和$y之间的匹配项”;
a=“${batches[“$x”]}”;
b=“${batches[“$y”]}”;
如果[-z$a]&&[-n$b]
然后回显“$line”>>输出/batch_$b.txt;
elif[-n$a]&&[-z$b]
然后回显“$line”>>Output/batch_$a.txt;
elif[-z$a]&&[-z$b]
然后回显“$line”>>输出/batch_0.txt;
elif[$a-gt$b]
然后回显“$line”>>Output/batch_$a.txt;
elif[$a-le$b]
然后回显“$line”>>输出/batch_$b.txt;
fi
完成
…然后:

if [[ $a && ! $b ]]; then
    write_to_file "Output/batch_$a.txt" "$line"
elif [[ ! $a ]] && [[ $b ]]; then
    write_to_file "Output/batch_$b.txt" "$line"
elif [[ ! $a ]] && [[ ! $b ]]; then
    write_to_file "Output/batch_0.txt" "$line"
elif (( a > b )); then
    write_to_file "Output/batch_$a.txt" "$line"
else
    write_to_file "Output/batch_$b.txt" "$line"
fi

请注意,缓存FD只有在您的输出文件足够少的情况下才有意义,您可以为每个FD维护打开的文件描述符(并且重新打开接收多个写入的文件是一个净好处)。请随意忽略这一点,只有在对您没有意义的情况下才进行更快的字符串拆分


最后,这里是另一种方法(也是使用自动fd管理编写的,因此需要Bash4.2)——运行两个cut调用,并让它们在整个输入文件中运行

exec {x_columns_fd}< <(cut -d"   " -f1 <input.txt)
exec {y_columns_fd}< <(cut -d"   " -f2 <input.txt)
while IFS='' read -r line && \
      IFS='' read -r -u "$x_columns_fd" x && \
      IFS='' read -r -u "$y_columns_fd" y; do
  ...
done <input.txt
exec{x_columns\u fd}<
…然后:

if [[ $a && ! $b ]]; then
    write_to_file "Output/batch_$a.txt" "$line"
elif [[ ! $a ]] && [[ $b ]]; then
    write_to_file "Output/batch_$b.txt" "$line"
elif [[ ! $a ]] && [[ ! $b ]]; then
    write_to_file "Output/batch_0.txt" "$line"
elif (( a > b )); then
    write_to_file "Output/batch_$a.txt" "$line"
else
    write_to_file "Output/batch_$b.txt" "$line"
fi

请注意,缓存FD只有在您的输出文件足够少的情况下才有意义,您可以为每个FD维护打开的文件描述符(并且重新打开接收多个写入的文件是一个净好处)。请随意忽略这一点,只有在对您没有意义的情况下才进行更快的字符串拆分


最后,这里是另一种方法(也是使用自动fd管理编写的,因此需要Bash4.2)——运行两个cut调用,并让它们在整个输入文件中运行

exec {x_columns_fd}< <(cut -d"   " -f1 <input.txt)
exec {y_columns_fd}< <(cut -d"   " -f2 <input.txt)
while IFS='' read -r line && \
      IFS='' read -r -u "$x_columns_fd" x && \
      IFS='' read -r -u "$y_columns_fd" y; do
  ...
done <input.txt

exec{x\u columns\u fd}<我已经在
awk
perl
:)中重写了它,这绝对是我最后的解决方案。我唯一的另一个建议是摆脱
cut
的使用,并使用内置的bash特性来分割行。顺便说一句,您有一些引用问题。就此而言,大多数ksh实现将比bash快得多,并且这些不会迫使您放弃对maps的支持……但是,是的,一定要摆脱cut;这将是这里最慢的事情。我要用
awk
perl
:)重写它,这绝对是我最后的解决方案。我唯一的另一个建议是,放弃
cut
的使用,使用内置的bash特性来分割行。顺便说一句,您有一些引用问题。就此而言,大多数ksh实现将比bash快得多,并且这些不会迫使您放弃对maps的支持……但是,是的,一定要摆脱cut;这将是这里最慢的事情。原始代码在每次迭代中分叉六次,而这个版本根本没有分叉。在我的系统上,原来的代码每10k行需要24.1秒,而这个版本需要0.39秒。@如果你是为基准测试而设置的,你能试试缓存文件句柄的新版本吗?噢,我的第一个剪切是在
声明-a输出\u fds
中缺少一个
-g
,从而使它在本地运行,而不缓存任何东西(但是浪费了很多FD)顺便说一下,如果在进行这些调整之后,如果BASH的性能仍然不够好,我会考虑/建议将其移植到KSH93,这是POSIX SuSuShell外壳,它还具有所有必需的闪亮特性,而且在AWK附近也有性能。谢谢。看起来我的BASH不支持声明-G。让我试试别的。Orginal代码在每次迭代中会分叉六次,而这个版本根本不会分叉。在我的系统中,原始代码在10k行中需要24.1s,而这个版本需要0.39s。@如果你是为基准测试而设置的,你能尝试一下缓存文件句柄的新版本吗?噢,我的第一个剪辑是
-g
decl中缺少一个
-g
是一个输出_fds
,因此使其在本地运行,而不缓存任何内容(但浪费大量fds)顺便说一下,如果在进行这些调整之后,如果BASH的性能仍然不够好,我会考虑/建议将其移植到KSH93-一个POSIX SuSuShell外壳,它也具有所有必需的闪亮特性,而且在AWK附近也有性能。谢谢。看起来我的BASH不支持声明-G。让我试试别的。