过滤大文件的最快方法

过滤大文件的最快方法

我有一个每分钟 4GB 大小的系统日志文件,下面是每分钟 2 行,共 450 万行,

我想生成一个只有几列的新文件eventtime|srcip|dstip,因此结果如下

1548531299|X.X.X.X|X.X.X.X

请注意,字段的位置是随机的。
我尝试了一些过滤器,但在具有 4 个核心和 16GB RAM 的强大 VM 机器上处理一个文件仍然需要 40 多分钟。

那么有没有一种方法可以处理这么大的文件并快速过滤所需的列?

{Jan 26 22:35:00 172.20.23.148 date=2019-01-26 time=22:34:59 devname="ERB-03" devid="5KDTB18800169" logid="0000000011" type="traffic" subtype="forward" level="warning" vd="Users" eventtime=1548531299 srcip=X.X.X.X srcport=3XXXX srcintf="GGI-cer.405" srcintfrole="undefined" dstip=X.X.X.X dstport=XX dstintf="hh-BB.100" dstintfrole="undefined" sessionid=xxxxxxx proto=6 action="ip" policyid=5 policytype="policy" service="HTTP" appcat="unscanned" crscore=5 craction=xxxxxx crlevel="low"

Jan 26 22:35:00 172.20.23.148 date=2019-01-26 time=22:34:59 devname="XXX-XX-FGT-03" devid="XX-XXXXXXXX" logid="0000000013" type="traffic" subtype="forward" level="notice" vd="Users" eventtime=1548531299 srcip=X.X.X.X srcport=XXXXX srcintf="XXX-Core.123" srcintfrole="undefined" dstip=X.X.X.X dstport=XX dstintf="sXX-CC.100" dstintfrole="undefined" sessionid=1234567 cvdpkt=0 appcat="unscanned" crscore=5 craction=123456 crlevel="low"}

答案1

Perl 来救援

将以下脚本保存为filter.pl并使其可执行(chmod +x):

#!/usr/bin/env perl

use strict;
use warnings;

while( <> ) {
    if ( /^(?=.*eventtime=(\S+))(?=.*srcip=(\S+))(?=.*dstip=(\S+)).*$/ ) {
        print "$1|$2|$3\n";
    }
}

然后运行

pduck@ubuntu:~> time ./filter.pl < input.txt > output.txt

real    0m44,984s
user    0m43,965s
sys     0m0,973s

正则表达式使用环视四周模式,在本例中为积极展望,以任意顺序匹配eventtimesrcip、 和三个值。dstip

我复制了你的两行输入,直到得到一个 4 GB 大小、约 900 万行的文件。我在 SSD 上运行了代码。

答案2

如果您想要一个真正快速的解决方案,我建议您使用 flex 工具。Flex 生成 C。以下内容能够处理像接受自由订单字段那样的示例。因此,创建一个文件,其名称f.fl为以下内容:

%option main
%%
  char e[100], s[100], d[100];

eventtime=[^ \n]*   { strcpy(e,yytext+10); }
srcip=[^ \n]*       { strcpy(s,yytext+6);  }
dstip=[^ \n]*       { strcpy(d,yytext+6);  }
\n                  { if (e[0] && s[0] && d[0] )printf("%s|%s|%s\n",e,s,d); 
                      e[0]=s[0]=d[0]=0 ;}
.                   {}
%%

测试尝试:

$ flex -f -o f.c f.fl 
$ cc -O2 -o f f.c
$ ./f < input > output

比较如下time

$ time ./f < 13.5-milion-lines-3.9G-in-file > out-file
real    0m35.689s
user    0m34.705s
sys     0m0.908s

答案3

我将您的两行“输入”复制到一个文件大小为 3867148288 字节(3.7 GiB)的文件中,我可以grep在 8 分 24 秒内处理它(从 HDD 读取和写入。使用 SSD 或 RAM 驱动器应该更快)。

为了尽量减少所用时间,我只使用了 的标准功能grep,并且没有对其进行后期处理,因此输出格式不是您指定的格式,但无论如何可能都很有用。您可以测试此命令

time grep -oE -e 'eventtime=[0-9]* ' \
 -e 'srcip=[[:alnum:]]\.[[:alnum:]]\.[[:alnum:]]\.[[:alnum:]]' \
 -e 'dstip=[[:alnum:]]\.[[:alnum:]]\.[[:alnum:]]\.[[:alnum:]]' \
infile > outfile

两行输出:

$ cat outfile
eventtime=1548531298 
srcip=X.X.X.Y
dstip=X.X.X.X
eventtime=1548531299 
srcip=X.X.X.Z
dstip=X.X.X.Y

输出文件包含 25165824 行,对应输入文件中的 8388608(830 万)行。

$ wc -l outfile
25165824 outfile
$ <<< '25165824/3' bc
8388608

我的测试表明grep每分钟可以处理大约 100 万行。

除非你的电脑比我的快得多。这还不够快,而且我认为你必须考虑一些快几倍的东西,可能在写入日志文件之前进行过滤,但是最好完全避免输出不必要的内容(并避免过滤)。


输入文件是通过复制生成的,也许系统会“记住”它之前见过相同的行,从而使处理速度更快,所以我不知道它在处理包含所有不可预测变化的真正大文件时会有多快。你必须测试它。


编辑 1:我在配备英特尔第四代 i7 处理器和 SSD 的 Dell M4800 上运行了同样的任务。它用了 4 分 36 秒完成,速度几乎翻倍,达到每分钟 182 万行。

$ <<< 'scale=2;25165824/3/(4*60+36)*60/10^6' bc
1.82

还是太慢了。


编辑2:我简化了grep模式并在戴尔中再次运行它。

time grep -oE -e 'eventtime=[^\ ]*' \
 -e 'srcip=[^\ ]*' \
 -e 'dstip=[^\ ]*' \
infile > out

耗时 4 分 11 秒,略有提升,达到每分钟 200 万行

$ <<< 'scale=2;25165824/3/(4*60+11)*60/10^6' bc
2.00

编辑 3:@JJoao,perl 扩展速度高达grep39 秒,相当于计算机每分钟 1290 万行,而普通grep读取速度为每分钟 100 万行(从 HDD 读取和写入)。

$ time grep -oP '\b(eventtime|srcip|dstip)=\K\S+' infile >out-grep-JJoao

real    0m38,699s
user    0m31,466s
sys     0m2,843s

这个 perl 扩展是经验性的,info grep但可以在我的 Lubuntu 18.04.1 LTS 中使用。

'-P' '--perl-regexp' 将模式解释为 Perl 兼容正则表达式 (PCRE)。这是实验性的,特别是与 '-z' ('--null-data') 选项结合使用时,并且 'grep -P' 可能会警告未实现的功能。*注意其他选项::。

我也按照@JJoao的flex方法编译了一个C程序,它在53秒内完成,相当于计算机每分钟949万行,而普通grep读取速度为每分钟100万行(从硬盘读取和写入)。 两种方法都很快,但grep使用perl扩展是最快的。

$ time ./filt.jjouo < infile > out-flex-JJoao

real    0m53,440s
user    0m48,789s
sys 0m3,104s

编辑 3.1:在配备 SSD 的 Dell M4800 中,我得到了以下结果,

time ./filt.jjouo < infile > out-flex-JJoao

real    0m25,611s
user    0m24,794s
sys 0m0,592s

time grep -oP '\b(eventtime|srcip|dstip)=\K\S+' infile >out-grep-JJoao

real    0m18,375s
user    0m17,654s
sys 0m0,500s

这对应于

  • flex应用程序每分钟 1966 万行
  • grep使用 perl 扩展每分钟 2735 万行

-f编辑 3.2:在带有 SSD 的 Dell M4800 中,当我使用flex 预处理器选项时,我得到了以下结果,

flex -f -o filt.c filt.flex
cc -O2 -o filt.jjoao filt.c

结果得到了改善,现在flex应用程序显示速度最高

flex -f ...

$ time ./filt.jjoao < infile > out-flex-JJoao

real    0m15,952s
user    0m15,318s
sys 0m0,628s

这对应于

  • 该应用程序每分钟3155万行flex

答案4

这是一个可能的解决方案,基于这个答案, 由...提供 @PerlDuck不久以前:

#!/bin/bash
while IFS= read -r LINE
do
    if [[ ! -z ${LINE} ]]
    then
        eval $(echo "$LINE" | sed -e 's/\({\|}\)//g' -e 's/ /\n/g' | sed -ne '/=/p')
        echo "$eventtime|$srcip|$dstip"
    fi
done < "$1"

我不知道它在如此大的文件上会如何表现,在我看来,awk解决方案会快得多。以下是它如何使用提供的输入文件示例:

$ ./script.sh in-file
1548531299|X.X.X.X|X.X.X.X
1548531299|X.X.X.X|X.X.X.X

time以下是在配备 SSD 和 16GB RAM 的普通 i7 上进行的生产力测试的结果:

$ time ./script.sh 160000-lines-in-file > out-file

real    4m49.620s
user    6m15.875s
sys     1m50.254s

相关内容