【问题标题】:Perl script faster than grep -fPerl 脚本比 grep -f 更快
【发布时间】:2026-01-28 20:15:01
【问题描述】:

我正在调整此处提出的现有 perl 脚本: Fast alternative to grep -f

我需要过滤许多非常大的文件(地图文件),每个约 1000 万行长 x 5 个字段宽,使用一个同样长的列表(过滤文件)并在地图文件中打印匹配的行。我尝试使用 grep -f,但它只是花费了太长时间。我读到这种方法会更快。

这是我的文件的样子:

过滤文件:

DB775P1:276:C2R0WACXX:2:1101:10000:77052
DB775P1:276:C2R0WACXX:2:1101:10003:51920
DB775P1:276:C2R0WACXX:2:1101:10004:36433
DB775P1:276:C2R0WACXX:2:1101:10004:57256

地图文件:

DB775P1:276:C2R0WACXX:2:1101:10000:70401     chr5    21985760    21985780    - 
DB775P1:276:C2R0WACXX:2:1101:10000:77052     chr18   14723904    14723924    - 
DB775P1:276:C2R0WACXX:2:1101:10000:77052     chr18   14745586    14745606    - 
DB775P1:276:C2R0WACXX:2:1101:10000:77052     chr4    7944241     7944261     - 
DB775P1:276:C2R0WACXX:2:1101:10000:77052     chr4    8402856     8402876     + 
DB775P1:276:C2R0WACXX:2:1101:10000:77052     chr8    10864708    10864728    + 
DB775P1:276:C2R0WACXX:2:1101:10002:88487     chr17   5681227     5681249     - 
DB775P1:276:C2R0WACXX:2:1101:10004:74842     chr13   2569168     2569185     + 
DB775P1:276:C2R0WACXX:2:1101:10004:74842     chr14   13253418    13253435    - 
DB775P1:276:C2R0WACXX:2:1101:10004:74842     chr14   13266344    13266361    -

我希望输出行看起来像这样,因为它们包含地图和过滤器文件中存在的字符串。

DB775P1:276:C2R0WACXX:2:1101:10000:77052     chr18   14723904    14723924    - 
DB775P1:276:C2R0WACXX:2:1101:10000:77052     chr18   14745586    14745606    - 
DB775P1:276:C2R0WACXX:2:1101:10000:77052     chr4    7944241     7944261     - 
DB775P1:276:C2R0WACXX:2:1101:10000:77052     chr4    8402856     8402876     + 
DB775P1:276:C2R0WACXX:2:1101:10000:77052     chr8    10864708    10864728    + 

这是我迄今为止编辑的脚本,但没有运气:

#!/usr/bin/env perl
use strict;
use warnings;

# Load the files
my $filter = $ARGV[0];
my $sam = $ARGV[1];
open FILE1, $filter;
   if (! open FILE1, $filter) {die "Can't open filterfile: $!";}
open FILE2, $sam;
   if (! open FILE2, $sam) {die "Can't open samfile: $!";}

# build hash of keys using lines from the filter file
my $lines;
my %keys
while (<FILE1>) {
   chomp $lines;
   %keys = $lines;
}
close FILE1;

# look up keys in the map file, if match, print line in the map file.
my $samlines;
while (<FILE2>) {
   chomp $samlines;
   my ($id, $chr, $start, $stop, $strand)  = split /\t/, $samline;
   if (defined $lines->{$id}) { print "$samline \n"; }
}

【问题讨论】:

  • grep is absurdly fast。也许你可以更快地写一些东西,但最终你仍然是从磁盘线性读取东西。相反,我会考虑将数据放入数据库中。
  • 是的,通常我使用 grep -f 来完成类似的任务。事实上,我一直在运行它,同时尝试解决运行速度更快的问题。然而,24 小时后, grep -f 仍然没有完成手头的工作之一。试图做出可敬的尝试,以更快地提出一些建议。
  • 将其放入数据库并在那里进行查询可能会更快。 (另外,好名字)我建议您尝试这种方法与您自己的方法并行。
  • 文件 IO 的限制因素几乎总是文件 IO。使用什么工具并不重要 - 磁盘旋转得如此之快。优化可能是可行的,例如将文件预加载到内存/数据库/更快的磁盘中。

标签: perl grep


【解决方案1】:

您似乎并没有真正尝试自己解决这个问题。您显示的代码甚至无法编译

它不工作的原因有几个

  • 您正在使用带有隐式控制变量的文件读取循环,这些变量将每一行读入$_,但您以某种方式期望数据出现在$lines$samlines 中。您还使用了$samline,您甚至没有声明

  • 线

    my %keys
    

    最后需要一个分号

  • 我不知道你期望在$lines 中是什么,但是将标量值分配给这样的哈希

    %keys = $lines;
    

    将产生警告散列分配中的元素数量为奇数,并留下一个只有一个元素的散列

这是一个 Perl 程序,我相信它会按照您的意图执行,但我不能说它是否会比 command_line grep 快得多。请注意,我使用了autodie pragma,而不是显式测试每个文件 IO 操作的状态

#!/usr/bin/env perl

use strict;
use warnings;
use v5.10.1;
use autodie;

my ($filter_f, $sam_f) = @ARGV;

my %filter;

{
    open my $fh, '<', $filter_f;

    while ( <$fh> ) {
        $filter{$1} = 1 if /(\S+)/;
    }
}

open my $fh, '<', $sam_f;

while ( <$fh> ) {
    print if /(\S+)/ and $filter{$1};
}

输出

DB775P1:276:C2R0WACXX:2:1101:10000:77052     chr18   14723904    14723924    -
DB775P1:276:C2R0WACXX:2:1101:10000:77052     chr18   14745586    14745606    -
DB775P1:276:C2R0WACXX:2:1101:10000:77052     chr4    7944241     7944261     -
DB775P1:276:C2R0WACXX:2:1101:10000:77052     chr4    8402856     8402876     +
DB775P1:276:C2R0WACXX:2:1101:10000:77052     chr8    10864708    10864728    +

【讨论】:

  • 非常感谢!在我的辩护中,这是我第一次尝试用 Perl 编写任何东西。永远。
  • 代码在测试数据上运行良好,但是当我在完整文件上运行它时,我得到:'perl: warning: Falling back to the standard locale ("C"). Out of memory! Out of memory! perl: warning: Setting locale failed. perl: warning: Please check that your locale settings: LANGUAGE = (unset), LC_ALL = (unset), LANG = "en_US.UTF-8" are supported and installed on your system.
  • @RedPandaSpaceOdyssey 那个过滤器文件有多大?
  • 地图文件有 48 个,每个文件一个过滤器文件。过滤器文件从6.9到31M,地图文件从400M到3.5G。
  • 那么我看不到您的程序如何产生 Out of memory! 错误。过滤器哈希应该占用不超过大约 300MB,并且一次读取一行映射文件。你写的东西与我的解决方案不同吗?
【解决方案2】:

所以,鲍罗丁提议的剧本确实有效。但是,我发现我的文件太大而无法完成。相反,我使用 'sort' 对两个文件进行排序,然后使用 join 进行排序。

join -1 1 -2 1 filter.file map.file > filtered.map

对于 48 个作业中的每一个,我都保留了 16G 的 RAM 和 8 个处理器。

感谢大家对此的帮助!

【讨论】: