如何使这个sed脚本更快?

我已经inheritance这个sed脚本片段,试图删除某些空的空间:

s/[\s\t]*|/|/g s/|[\s\t]*/|/g s/[\s] *$//g s/^|/null|/g 

在一个大约1Gb的文件上运行。 这个脚本在我们的unix服务器上运行2个小时。 任何想法如何加快?

注意\代表一个空格,\ t代表一个制表符,实际的脚本使用实际的空格和制表符而不是那些符号

input文件是一个pipe道分隔文件,并位于本地不在networking上。 这四行是在sed -f执行的文件中

我能用sed做的最好的是这个脚本:

 s/[\s\t]*|[\s\t]*/|/g s/[\s\t]*$// s/^|/null|/ 

在我的测试中,这比你的sed脚本运行速度快了30%。 性能的提高来自前两个正则表达式的组合,省略了不需要的“g”标志。

然而,30%的速度只是一个轻微的改进(在1GB的数据文件上运行上述脚本仍需要大约一个半小时的时间)。 我想看看我能做得更好。

最后,没有其他方法我尝试了(awk,perl和sed的其他方法),除了 – 当然是一个普通的C实现。 正如C预期的那样,这里的代码有点冗长,但是如果你想要一个程序比其他任何方法都快,你可能需要看看它 。

在我的测试中,C实现在大约20%的sed脚本所花费的时间内完成。 所以在你的Unix服务器上运行大约需要25分钟。

我没有花太多的时间来优化C的实现。 毫无疑问,有很多地方可以改进算法,但坦率地说,我不知道是否有可能削减大量的时间。 如果有的话,我认为它肯定会对其他方法(sed,awk,perl,python等)所能达到的性能有一个上限。

编辑:原始版本有一个小错误,导致它可能在输出结束时打印错误的东西(例如,可能会打印一个“空”,不应该在那里)。 我今天有一段时间来看看并解决这个问题。 我还优化了一个对strlen()的调用,使得它又有了一点提升。

我的测试表明,sed可以很容易地在这样的事情上被绑定到cpu上。 如果你有一个多核心的机器,你可以尝试使用一个如下所示的脚本来产生多个sed进程:

 #!/bin/sh INFILE=data.txt OUTFILE=fixed.txt SEDSCRIPT=script.sed SPLITLIMIT=`wc -l $INFILE | awk '{print $1 / 20}'` split -d -l $SPLITLIMT $INFILE x_ for chunk in ls x_?? do sed -f $SEDSCRIPT $chunk > $chunk.out & done wait cat x_??.out >> output.txt rm -f x_?? rm -f x_??.out 

从你的例子看来,你正在清理文本文件中管道(|)分隔字段开头和结尾的空格。 如果我这样做,我会改变算法如下:

 for each line split the line into an array of fields remove the leading and trailing white space join the fields back back together as a pipe delimited line handling the empty first field correctly. 

我也会使用不同的语言,如Perl或Ruby。

这种方法的优点是,清理行的代码现在可以为每个调用处理更少的字符,并且即使需要更多的调用也应该更快地执行。

尝试将前两行改为:

 s/[ \t]*|[ \t]*/|/g 

这个Perl脚本应该快得多

 s/\s*|\s*/|/go; s/\s *$//o; s/^|/null|/o; 

基本上,确保您的正则表达式编译一次('o'标志),并且不需要在仅应用于结尾和行首的正则表达式上使用'g'。

另外,[\ s \ t] *相当于\ s *

这可能工作。 我只测试了一下。

 awk 'BEGIN {FS="|"; OFS="|"} {for (i=1; i<=NF; i++) gsub("[ \t]", "", $i); $1=$1; if ( $1 == "" ) $1 = "null"; print}' 

Perl如何:

 #!/usr/bin/perl while(<>) { s/\s*\|\s*/|/g; s/^\s*//; s/\s*$//; s/^\|/null|/; print; } 

编辑:改变方法显着。 在我的机器上,这比你的sed脚本快了近3倍。

如果你真的需要最好的速度,写一个专门的C程序来完成这个任务。

使用gawk,而不是sed。

 awk -vFS='|' '{for(i=1;i<=NF;i++) gsub(/ +|\t+/,"",$i)}1' OFS="|" file 

尝试使用一个命令:

 sed 's/[^|]*(|.*|).*/\1/' 

你尝试过Perl吗? 这可能会更快。

 #!/usr/local/bin/perl -p s#[\t ]+\|#|#g; s#\|[\t ]+#|#g; s#[\t ]*$##; s#^\|#null|#; 

编辑:实际上,它似乎比sed程序慢大约三倍。 奇怪…

我认为在正则表达式中的问题和大多数答案可以是一个重大的放缓相比,使用+ 。 考虑问题中的第一个替换

 s/[\s\t]*|/|/g 

*匹配零个或多个项目,后跟一个| ,因此每个| 被替换,甚至那些不需要更换。 改变替换

 s/[\s\t]+|/|/g 

只会改变| 前面有一个或多个空格和制表符的字符。

我没有sed可用,但我做了一个Perl的实验。 在数据上,我使用*的脚本比使用+的脚本长将近7倍。

整个运行过程中的时间是一致的。 最小和最大时间的差异是平均值的4%, *是3.6%。 + :: *的平均时间比为1 :: 6.9。

实验细节

使用一个80MB文件进行测试,超过18万次出现[st]\. ,这些是小写字母st

测试使用了一个批处理命令文件,每个命令文件有30个,星号和加号交替。

 perl -f TestPlus.pl input.ltrar > zz.oo perl -f TestStar.pl input.ltrar > zz.oo 

一个脚本在下面,另一个脚本只是把*改成+plus star plus

 #! /bin/usr/perl use strict; use warnings; use Time::HiRes qw( gettimeofday tv_interval ); my $t0 = [gettimeofday()]; while(<>) { s/[st]*\././g; } my $elapsed = tv_interval ( $t0 ); print STDERR "Elapsed star $elapsed\n"; 

使用的Perl版本:

 c:\test> perl -v This is perl 5, version 16, subversion 3 (v5.16.3) built for MSWin32-x64-multi-thread (with 1 registered patch, see perl -V for more detail) Copyright 1987-2012, Larry Wall Binary build 1603 [296746] provided by ActiveState http://www.ActiveState.com Built Mar 13 2013 13:31:10