【问题标题】:Optimize CSV processing script - Python, Perl, and Java [closed]优化 CSV 处理脚本 - Python、Perl 和 Java [关闭]
【发布时间】:2014-10-06 05:13:05
【问题描述】:

我正在尝试制作一个简单的脚本来快速处理嘈杂的 CSV 文件。 我只想从一个大的 CSV 文件(gzipped)中抓取几列,然后用修剪后的数据编写一个新的 CSV 文件。还添加了一种简单的过滤方法,检查 columns[0] == 15 的长度。

我比较了 perl、java 和 python 脚本,发现 Java 比其他语言快得多。我想知道是否有任何其他方法可以针对每种语言优化这个简单的过程?

每种语言的基准时间是(对于 800MByte gzip 文件) 1.Java:74秒 2. Python:197 秒 3. Perl:7 分钟

Python:

import gzip
import csv
import time

def getArray(row): 
    columns = [0,4,5,26,33,34,35,36,39,41,42,47,54,65,66,72,73,91]
    row_filt = []
    for i in columns:
        row_filt.append(row[i])
    return row_filt

filename = 'Very_large_csv.gz' 
outfile = filename + '.csv'
csv.register_dialect('wifi', delimiter='|', quoting=csv.QUOTE_NONE, quotechar = '')
start_time = time.time()

try:
    f = gzip.open(filename, 'rb')
    f2 = open(outfile, 'wb')
    reader = csv.reader(f, dialect = 'wifi')
    writer = csv.writer(f2, dialect = 'wifi')
    header = reader.next()
    writer.writerow(getArray(header))
    for row in reader:
        if (len(row[0]) != 15):
            continue
        writer.writerow(getArray(row))
    print(time.time() - start_time)

finally:
    f.close()

Perl:

use strict;
use warnings;
use Cwd;
use IO::Uncompress::Gunzip qw($GunzipError);
use Text::CSV_XS;
use Time::Piece;
use Time::Seconds;

my @COLUMNS = (0,4,5,26,33,34,35,36,39,41,42,47,54,65,66,72,73,91);

my $csv = Text::CSV_XS->new ({  binary => 1,
                                sep_char => '|',
                                escape_char => undef,
                                eol => "\n",
                                quote_char => undef
                                });

my $infile='Very_large_csv.gz';

my $fh = IO::Uncompress::Gunzip->new($infile) or die "IO::Uncompress::Gunzip failed: $GunzipError\n";

my $outfile = $infile . ".csv";
open my $out, ">", $outfile or die "$outfile: $!\n";

my @header_row = split(/\|/,<$fh>);
my @header = ();
foreach my $column (@COLUMNS)
{
    push @header, $header_row[$column];
}
my $header_filter = \@header;   
$csv->print ($out, $header_filter);

print "Start.\n";
while (my $row = $csv->getline($fh))
{
    length($row->[0]) == 15 or next;
    my @data = ();
    foreach my $column (@COLUMNS)
    {
        push @data, $row->[$column];
    }
    my $row_filter = \@data;

    $csv->print($out, $row_filter); 
}
$csv->eof or $csv->error_diag ();

close $fh;
close $out or die "$outfile: $!";

Java:

public class NoiseFilter {
    static final int[] columns = {0,4,5,26,33,34,35,36,39,41,42,47,54,65,66,72,73,91};

    public static void main(String[] args) throws IOException {
        fname='Very_large_csv.gz';
        GZIPInputStream gzip = new GZIPInputStream(new FileInputStream(fname));
        BufferedReader reader = new BufferedReader(new InputStreamReader(gzip));

        String line = reader.readLine(); // Header
        String[] header = line.split("\\|");
        PrintWriter ww = new PrintWriter(fname + ".csv");
        printRow(header, ww);

        while ((line = reader.readLine()) != null) {
            String[] data = line.split("\\|",-1);
            if (data[0].length() != 15 ) { continue; }
            printRow(data, ww);
        }

        ww.close();
        reader.close();
    }

    private static void printRow(String[] row, PrintWriter writer) {
        for (int i = 0; i < columns.length; i++) {
            if (i == 0) {
                writer.print(row[columns[i]]);
            } else {
                writer.print("|" + row[columns[i]]);
            }
        }

        writer.print("\n");
    }
}

我对python代码进行了如下修改,得到了95sec的运行时间,与Java相当。

def getArray(line): 
    string=''
    row=line.split(',')
    for i in columns:   
        string+=(row[i]+',')
    return string+'\n'

try:
    f = gzip.open(filename, 'rb')
    f2 = open(outfile, 'wb')

    header = f.readline() 
    f2.write(getArray(header))

    for line in f:
        f2.write(getArray(line))
finally:
    f.close()

【问题讨论】:

  • 我在没有使用 csv 阅读器的情况下更改了 python。使用“row=line.split('|')”和拼接字符串“s+=(row[i]+'|')”,运行时间减少到112秒,与java相当。

标签: java python perl parsing csv


【解决方案1】:

你的内循环没有很多脂肪。在 python 版本中,每次调用 getarray() 时都会构造一个新的列对象。由于 getarray() 函数本身非常简单,因此您可以将整个内容内联。

不太可能是显着的加速。

您也可以尝试 PyPy,它可能会产生相对较大的差异——但可能仍然不如 Java 版本快。

【讨论】:

  • 我已经使用“line.split(',')”更改了代码并尝试了pypy,但pypy似乎比通用python慢​​。
【解决方案2】:

可以在您的 Perl 脚本中优化某些内容。例如,这个:

while (my $row = $csv->getline($fh))
{
    length($row->[0]) == 15 or next;
    my @data = ();
    foreach my $column (@COLUMNS)
    {
        push @data, $row->[$column];
    }
    my $row_filter = \@data;

    $csv->print($out, $row_filter); 
}

可以替换为:

my $row;
length($row->[0])==15 and $csv->print($out, [ @{$row}[@COLUMNS] ])
    while $row = $csv->getline($fh);

...应该表现更好一些。我没有对其进行基准测试,但它不太可能产生巨大的差异。

更重要的是,您的 Java 代码速度更快的原因是它做得更少。 Text::CSV_XS(我猜你也使用了 Python 模块)是一个完整的解析器——它处理带引号的字段、转义字符等。考虑以下以管道分隔的文件,该文件旨在为两行两列:

1|"Foo+Bar"
2|"Foo|Bar"

您的 Java 代码天真地拆分管道上的行,这意味着应该是单个原子字符串值的“Foo|Bar”被拆分为两个字段。如果 Java 代码执行与 Perl 和 Python 版本相同的检查,它会立即变慢。

相反,您可以通过放弃正确的 CSV 样式解析并仅使用 split 来加速 Perl 或 Python 版本。例如在 Perl 中:

while (<$fh>) {
    chomp;
    my @F = split /\|/;
    length $F[0] == 15 or next;
    print {$out} join("|", @F[@COLUMNS]), "\n";
}

您的整个脚本甚至可以使用以下单行代码完成:

gzip -d -c Very_large_csv.gz | perl -F'\|' -lane 'print join("|", @F[0,4,5,26,33,34,35,36,39,41,42,47,54,65,66,72,73,91]) if $. == 1 || length($F[0]) == 15' > output.csv

说明:

开关

  • -F: split() 模式用于-a 开关(// 是可选的)
  • -l:启用行尾处理
  • -a:在空间上分割行并将它们加载到数组中@F
  • -n:为输入文件中的每个“行”创建一个 while(&lt;&gt;){...} 循环。
  • -e:告诉perl 在命令行上执行代码。

代码

  • gzip -d -c Very_large_csv.gz: 解压文件,通过管道传输到 STDOUT
  • print join("|", @F[0,4,5,26,33,34,35,36,39,41,42,47,54,65,66,72,73,91]): 只保留 CSV 文件的某些索引
  • if $. == 1 || length($F[0]) == 15: 基于标题或第一列的过滤器

【讨论】:

    猜你喜欢
    • 2023-03-09
    • 2015-09-11
    • 2012-09-29
    • 1970-01-01
    • 2015-10-12
    • 2016-04-11
    • 2014-02-15
    • 1970-01-01
    • 2017-02-09
    相关资源
    最近更新 更多