【问题标题】:how can i read data from csv file and store in binary tree and write multiple file in perl如何从 csv 文件中读取数据并存储在二叉树中并在 perl 中写入多个文件
【发布时间】:2013-08-02 09:46:46
【问题描述】:

我有超过 100 万条数据的 CSV 文件。我想使用binary::tree 来减少内存使用量。

该程序主要用于搜索前5位并创建新文件(文件名应为前5位)以存储相同的前5位数据。

我的代码运行良好,但使用了高内存。

现在我用这个代码写:

my $file = "my_csv_file.csv";

open (my $data, '<', $file) or die "Could not open '$file' $!\n";

while (my $lines = <$data>) {
        my @fields = split "," , $lines unless $. == 1;

        my $first_five = substr ($fields[1],  0,  5,);

        if (-e "$first_five.csv" ) {
            open my $fh, '>>', "$first_five.csv" or die $!;
            print { $fh } $lines;

        }       else {
            open my $fh, '>>', "$first_five.csv" or die $!;
            print $fh "Title\n";
        }               
        close $fh;
}
 close $data;

【问题讨论】:

  • 我在这里看不到任何问题...
  • @Vince: 如何使用 binary::search 来缩短同一个程序的数据?
  • 我根本看不出它是如何使用大量实际内存的,因为它不会保留任何输入数据。它读取一行,将其写入文件然后忘记它。也许你通过为每一行获取一个新变量,并执行所有这些打开和关闭操作,来锻炼 Perl 的垃圾收集器。
  • @JoeZ:实际上我的文件中有超过 100 万行的大量数据。所以这个程序花费了太多时间,CPU 使用率也很高。任何其他解决方案..
  • 我当然可以看到它占用了大量的 CPU 时间,但并没有太多的内存。真正的问题是所有文件的打开/关闭。您正在打开/关闭 1,000,000 个文件。你可能最好把这个批处理。我将编写一个示例解决方案。

标签: perl csv binary-tree


【解决方案1】:

我相信脚本中的性能瓶颈根本不是内存使用,而是您为每条记录打开和关闭一个文件。如果我理解正确的单位,100 万是 1,000,000,所以打开和关闭的次数相当多。

一种解决方案是批量处理数据,特别是如果您在提取为键的“前 5 个”中有许多重复键。

我在第二个字段中包含 100 个唯一的 5 位键的合成文件上将您的程序与下面的程序进行了基准测试,但有 10,000,000 条记录(文件大小的 10 倍)。这些行看起来像这样:

1,9990001,----------------------------------------------------------------------------------------------------
2,9990002,----------------------------------------------------------------------------------------------------
3,9990003,----------------------------------------------------------------------------------------------------

我这样做是为了模拟输入中的大量数据。它应该是您输入文件的记录数的 10 倍左右。

您的原始脚本在我的计算机上处​​理此输入需要 2 多分钟。以下脚本使用 10,000 条记录的批次,耗时 24 秒。速度是原来的 5 倍以上。

my $file = "my_csv_file.csv";

open (my $data, '<', $file) or die "Could not open '$file' $!\n";

sub write_out
{
    my $batch = shift;

    for my $first_five (keys %$batch)
    {
        my $file_name  = $first_five . ".csv";
        my $need_title = ! -e $file_name;

        open my $fh, '>>', $file_name or die $!;
        print $fh "Title\n" if $need_title;
        print $fh @{ $batch->{$first_five} };
        close $fh;
    }
}

my ($line, $batch, $count);

$batch = { };
$count = 0;

while ($line = <$data>)
{
    next if $. == 1;

    if ($line =~ /^[^,]*,(.....)/)
    {
        push @{ $batch->{$1} }, $line;

        if (++$count > 10000)   # adjust as needed
        {
            write_out $batch;
            $batch = { };
            $count = 0;
        }
    }
}

write_out $batch if $count; # write final batch

close $data;

现在,我确实注意到了我的脚本输出与您的输出之间的一个区别:您的脚本似乎删除了每个目标 .csv 文件的第一行输出,将单词 Title 放在了它的位置。我认为这是一个错误。我上面的脚本添加了一个名为Title 的行,但没有删除给定“前五个”的第一个实例。

如果你想要以前的行为,你可以在sub write_out中改变它。

我做了一些额外的实验。我将批量大小更改为 10,000,000,这样 write_out 只会被调用一次。内存使用量确实增长了很多,运行时间仅下降到 22 秒。我还尝试将批量大小更改为 100。内存使用量急剧下降,但运行时间上升到大约 30 秒。这表明文件打开/关闭是真正的瓶颈。

因此,通过更改批处理大小,您可以控制内存占用与运行时间。无论如何,面向批处理的代码应该比您当前的方法快得多。

编辑:我使用第二个 1000 万条记录输入做了进一步的基准测试,这次完全随机化了 5 位键。结果输出写入了 100,000 个名为 00000.csv99999.csv 的文件。运行原始脚本大约需要 3 分钟,而我上面的脚本(批量大小为 1000000)大约需要 1:26,因此速度大约是原来的两倍。

瓶颈不是脚本本身,而是文件系统操作。创建/更新 100,000 个文件本身就很昂贵。

【讨论】:

    猜你喜欢
    • 2014-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-09
    • 2013-01-25
    • 2015-05-23
    • 1970-01-01
    相关资源
    最近更新 更多