【问题标题】:Processing 1.5 Million Line File under 5 Minutes在 5 分钟内处理 150 万行文件
【发布时间】:2014-05-01 15:13:41
【问题描述】:

在网上长时间搜索后,我决定在这里询问我的问题。我有一个 CSV 文件集(总共 36 个文件),每 5 分钟发送一次。每个文件包含大约 150 万行。我需要在 5 分钟内处理这些文件。我必须解析这些文件并在存储区域内从它们创建所需的目录。然后每个唯一的行将被翻译成一个文件并放在相关目录中。相关行也将写入相关文件中。如您所见,有很多 I/O 操作。

我可以在大约 10 分钟内完成总共 12 个文件。目标是在 5 分钟内完成 36 个。我正在使用 PERL 来完成这个操作。我看到的问题是 i/o 操作的系统调用。

我想在 Perl 中控制文件处理程序和 I/O 缓冲区,这样我就不必每次都去写入文件。这是我真正迷路的地方。加上创建目录似乎也消耗了太多时间。

我在 CPAN 和网络上搜索了一些线索,可以照亮我的道路,但没有运气。有人对那个主题有建议吗?我应该在哪里阅读或应该如何进行?我相信 Perl 完全有能力解决这个问题,但我想我没有使用正确的工具。

open(my $data,"<", $file);
my @lines = <$data>;

foreach (@lines) {
    chomp $_;
    my $line = $_;

    my @each = split(' ',$line);
    if (@each == 10) {
       my @logt = split('/',$each[3]);
       my $llg=1;

       if ($logt[1] == "200") {
           $llg = 9;
       }

       my $urln = new URI::URL $each[6];
       my $netl = $urln->netloc;

       my $flnm = md5_hex($netl);
       my $urlm = md5_hex($each[6]);

       if ( ! -d $outp."/".$flnm ) {
          mkdir $outp."/".$flnm,0644;
       }

       open(my $csvf,">>".$outp."/".$flnm."/".$time."_".$urlm) or die $!;
       print $csvf int($each[0]).";".$each[2].";".$llg."\n";
       close $csvf;   #--->> I want to get rid of this so I can use buffer      
    }
    else {
       print $badf $line;
    }

}

假设上面的代码在一个子程序中使用并且线程化了 12 次。上述代码的参数是 filename 。我想摆脱关闭。因为每次我打开和关闭文件时都会调用系统 I/O,这会导致速度变慢。这当然是我的假设,我更愿意接受任何建议

提前致谢

【问题讨论】:

  • 你在使用system()复制文件和制作目录吗?
  • 您是否已将其并行化,以便您的(可能的)4 个内核中的每一个可能获得 9 个文件?您是否使用出色的 search.cpan.org/perldoc/Devel::NYTProf 分析了您的代码
  • cat *csv &gt; /dev/null需要多长时间?
  • 您正在为 5400 万行中的每一行打开一个要追加的文件...
  • 我上一个问题的原因是我认为您最好将数据保存在内存中,然后最后全部写入磁盘。因此,使用文件名作为键创建一个散列,并将数据附加到散列元素的末尾,而不是写入文件。然后,当你到最后,或者你觉得内存中有足够的东西时,将它们全部刷新到真正的磁盘文件中。

标签: perl file csv io


【解决方案1】:

您可能会多次打开同一个文件。如果是这样,收集数据结构中的信息并仅在循环完成后写入文件可能会有所帮助。这样可以避免重复测试同一目录是否存在,并且每个输出文件只打开一次。

我们还应该摆脱URI::URL——考虑到您的性能要求,在每次循环迭代期间创建一个新对象的成本太高了。如果您的 URL 看起来都像 http://user:password@example.com/path/https://example.com/,我们可以改用简单的正则表达式。

open my $data, "<", $file or die "Can't open $file: $!";

my %entries;  # collect entries here during the loop

# only read one line at a time, don't keep unnecessary ballast around
while (my $line = <$data>) {
    chomp $line;

    my @each = split(' ',$line);

    if (@each != 10) {
        print $badf $line;
        next;
    }

    my (undef, $logt) = split('/', $each[3]);
    my $llg = ($logt == 200) ? 9 : 1;

    my $url = $each[6];
    my ($server) = $url =~ m{\A\w+://([^/]+)};

    push @{ $entries{$server}{$url} }, sprintf "%d;%s;%d\n", $each[0], $each[2], $llg;
}

while (my ($dir, $files) = each %entries) {
    my $dir_hash = md5_hex($dir);
    my $dirname = "$outp/$dir_hash";

    mkdir $dirname, 0644 or die "Can't create $dirname: $!" unless -d $dirname;

    while (my ($file, $lines) = each %$files) {
        my $file_hash = md5_hex($file);
        my $filename = "$dirname/${time}_${file_hash}";

        open my $csv_fh, ">>", $filename or die "Can't open $filename: $!";
        print { $csv_fh } @$lines;
    }
}

我还清理了代码的其他方面(例如变量命名、错误处理)。我将调用 md5_hex 移出主循环,但根据数据类型,最好不要延迟散列。

【讨论】:

  • 感谢阿蒙的努力。我会尝试这段代码并反馈给你。干杯
  • 我的 ($server) = $url =~ m{\A\w+://([^/]+)/};嗨,阿蒙,我在这里遇到了那篇文章的问题,你这样做的目的是什么?
  • 请忘记我的评论:(我找到了问题
  • 谢谢 Amon,看起来这对我有用,现在我将尝试将这段代码并行放置,看看会有什么效果。
  • @SunerEvren 正则表达式在给定有效 URI 时计算出权威部分(另请参见 generic URI syntax)。主机名部分可以是 IP 地址。但是,它本身不会匹配裸 IP 地址。我不知道你想匹配什么输入,所以我无法调整正则表达式。
猜你喜欢
  • 1970-01-01
  • 2023-03-26
  • 2015-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-28
  • 2022-12-18
相关资源
最近更新 更多