【问题标题】:What is the fastest way to parse text?解析文本的最快方法是什么?
【发布时间】:2010-08-23 18:30:41
【问题描述】:

假设我想提取在某个文本文件中找到的给定字符串后面的第一个单词(或浮点数)(请参阅How to extract the first word that follows a string?)。我知道您可以使用 perl 或 sed 以及可能许多其他方式来做到这一点。我正在寻找性能。最快的解析方式是什么?

【问题讨论】:

  • 这里的“性能”是什么,数据集有多大?期望什么性能,要提取多少数据?我记得我们从 100MB 晶体学数据输出文件中提取(大量)数据的案例——每个文件的提取过程最终大约需要 1-2 秒(包括文件读取)。

标签: string parsing


【解决方案1】:

如果你正在寻找一个固定的字符串,你可能想使用类似 Boyer-Moore 或 Boyer-Moore-Horspool 的东西来搜索它(对于后者,我推荐 Ray Gardner 的实现)。请注意,B-M 和 B-M-H 都是 次线性。相比之下,正则表达式充其量是线性的1,并且许多实现(使用回溯的实现)是二次的。

下一步是确保您尽快将数据读入内存。实际上,这通常会成为瓶颈。不幸的是,为了很好地处理瓶颈,您通常必须使用一些不可移植的代码。在 Linux 下,mmap 往往是您最好的选择,而在 Windows 下,通常最好一次读取大块,并使用 FILE_FLAG_NO_BUFFERING 标志调用 CreateFile。还值得使用 I/O 完成端口 (IOCP) 来执行读取,这样您就可以并行执行搜索和读取。

1理论上,可以编写一个 RE 引擎来进行亚线性搜索以搜索正确的模式类型——但如果有任何实际这样做,我不知道。

【讨论】:

  • 为什么使用FILE_FLAG_NO_BUFFERING?这不会禁用预读,导致更差吞吐量吗?
  • 它禁用预读,因此建议使用 IOCP(您可以显式控制预读)。问题是缓存的前提是您很快就会再次使用相同的数据,但在这种情况下,这不太可能。同时,它可以/将刷新缓存中确实很有可能被使用的数据——如果该数据是脏的,刷新它将需要将其写入磁盘......
【解决方案2】:

与大多数类似问题一样,我会实施您的特定案例并使用一系列适用工具对其进行衡量。

可能发现其他因素会影响您的数据,例如打开/读取文件等的速度,因此技术推荐将毫无价值。

【讨论】:

    【解决方案3】:

    大多数时候,您将从磁盘读取字符串,或者更糟糕的是,从网络读取它,这已经比您选择的任何理智的搜索方式慢了几个数量级。话虽如此,如果您的字符串已经在内存中,并且您预先知道您的搜索词,则可以保证正则表达式(真正的基于 FSM 的,而不是 Perl 的)在时间上与输入大小成线性关系——所以更快的是只会以一个常数因子更快。有一个名为 re2 的快速正则表达式引擎库。

    【讨论】:

    • 查看我的答案——“仅以常数因子更快”部分不一定正确。
    • 你是对的,如果整个字符串已经在内存中,你可以更快。我没有考虑它,因为大多数时候需要先阅读它,而这充其量是线性的。好吧,除非您要查找的字符串真的很长,这样您就可以跳过输入的大部分内容而根本不阅读/下载它......
    • 对——实际上,这可能没有多大意义,你的主要注意力应该放在快速阅读上。
    【解决方案4】:

    我敢打赌,如果这真的是一个简单的案例(不涉及真正的正则表达式),C 会赢(memchr() 和朋友),否则 TCL 将是一个强有力的竞争者,其次是 Perl(后者需要良好的制作技能用于正则表达式设计)。

    附录:再次阅读您的描述(以及随附的链接),我会说您应该测试一些工具并比较结果。您能否为我们(某处)提供一个输入文件并根据该文件指定确切的问题?

    附录 2(费曼): 我保存了这个站点(~44KB,'sof.dat')并进行了重复搜索测试 (正如您建议的那样:“网站设计/”)在 Perl 中。这是使用的代码:

     ...
     use Benchmark qw' timeit timestr ';
    
    {
      open my $fh, '<', 'sof.dat' or die $!;
      undef $/;
      our $data = <$fh>;
      close $fh;
    
       my $count = 100000;
       my  $t = timeit($count, ' $data =~ m|site design /| ? 1 : 0 '  );
       print "$count loops of code took:", timestr($t),"\n";
    }
    ...
    

    使用我的设置(2.4GHz E6600、Win7、Activeperl),出现以下时序:

    100000 次代码循环:1 挂钟秒 (1.36 usr + 0.00 sys = 1.36 CPU) @ 73746.31/s (n=100000)

    也就是说,我每秒可以在这个页面上找到这个文本大约 74000 次。

    如果我包含完整的文件处理:

    use Benchmark qw' timeit timestr ';
    
    {
       my $count = 10000;
       my  $t = timeit($count, q{
                       open my $fh, '<', 'sof.dat' or die $!;
                       undef $/;
                       our $data = <$fh>;
                       close $fh;
                       $data =~ m|site design /| ? 1 : 0
                       } );
       print "$count loops of code took:", timestr($t),"\n";
    }
    

    我们只会得到:

    10000 次代码循环:3 挂钟秒(1.70 usr + 1.51 sys = 3.21 CPU)@ 3110.42/s (n=10000)

    每秒大约 3000 次搜索/查找通道。

    问候

    rbo

    【讨论】:

    • 我的项目仍处于起步阶段。我想从一开始就选择一个好的方法。在这一点上,我没有任何异常长的数据文件。如果我可以编造一些,我将如何附上它们?
    • ok 怎么样:使用这个页面的 html 源文件。找到下面的文字,说“网站设计/”(没有引号)
    • 哇。谢谢!所以我想我最好用 C、bash 和 TCL 编写一个等效的程序。我有一个 bash 程序,但我只是从另一个论坛的一个线程中复制了代码。我真的不太了解这些语言(或者根本不了解 TCL 和珍珠)。我正在尝试自动为其他一些软件生成输入文件并从输出中提取行。我认为进行这个实验超出了我的范围。我只是学习基础知识,其余的从谷歌获得。感谢您的意见。
    猜你喜欢
    • 1970-01-01
    • 2010-09-22
    • 2016-10-27
    • 2021-04-12
    • 1970-01-01
    • 1970-01-01
    • 2011-12-18
    • 1970-01-01
    • 2010-09-14
    相关资源
    最近更新 更多