![Perl 命令性能问题](https://linux22.com/image/14501/Perl%20%E5%91%BD%E4%BB%A4%E6%80%A7%E8%83%BD%E9%97%AE%E9%A2%98.png)
我使用以下命令删除数字列(.CSV 文件)中的特殊字符,除此之外它工作正常,但这里的问题是性能。我的 CSV 文件编号列数据包含如下。为了删除数据中的 1000 个分隔符逗号,我使用了以下 Perl 命令。
payment
"4,326.34"
590.20
"12,499.40"
注意:我的文件分隔符是“,”逗号。
input file :
Organization,Amount,Revenue,Balance,Desc
Congos,"4,233.78","3,233.78","1,233.78",Payment
Toyoto,590.2,390.2,190.2,Payment
lenives,"5,234.89","2,234.89","1,234.89",Payment
Excepted OutPut:
Organization,Amount,Revenue,Balance,Desc
Congos,4233.78,3233.78,1233.78,Payment
Toyoto,590.2,390.2,190.2,Payment
lenives,5234.89,2234.89,1234.89,Payment
命令 : cat | perl -p -e 's/,(?=[\d,.]\d")//g and s/"(\d[\d,.])"/\1/g' 'test.csv' >> newfile.csv
文件数据计数:1100万条数据
问题:删除数据中 1000 个单独的“逗号”花了近 10 分钟。
有更好的解决方案来提高性能吗?
答案1
我将示例输入保存到文件中,然后复制数据行数百万次,使其包含大约 1100 万条记录:
$ wc -l input2.csv
11100445 input2.csv
$ ls -l input2.csv
-rw-r--r-- 1 cas cas 505070243 Apr 10 22:32 input2.csv
快速而肮脏的单行:
$ time perl -pe 's/"([0-9-.]+),?([0-9-.]+)"/$1$2/g' input2.csv >output.csv
real 0m37.922s user 0m36.371s sys 0m1.347s
"
这会从数字字段中去除逗号和引号 ( ) 字符。不幸的是,它只适用于其中只有一个逗号的数字字段 - 即下面的数字 1,000,000
。我认为不值得花精力想出一个匹配数字字段中多个逗号的正则表达式,而是使用 csv 解析库。
使用 Text::CSV 模块速度较慢,但更“正确”
此版本使用 CSV 解析 perl 模块,检查每个字段以查看它是否是数字,如果是,则删除所有逗号。这适用于任何值的数字字段。
它不需要删除数字字段周围的任何引号字符 - Text::CSV 会自动处理字段引号。
#!/usr/bin/perl
use Text::CSV;
my $filename = shift;
my $csv = Text::CSV->new;
open my $fh, $filename or die "$filename: $!";
while (my $row = $csv->getline ($fh)) {
my @row = map { tr/,//d if ( m/^[0-9,.-]+$/); $_ } @{ $row };
$csv->print(*STDOUT, \@row);
print "\n";
}
close($fh);
将其另存为,例如,strip.pl
并使其可执行chmod +x strip.pl
。
$ time ./strip.pl input2.csv > output2.csv
real 1m32.379s user 1m30.422s sys 0m1.609s
这可能可以优化很多,我没有花费任何努力让它更快 - 1.5 分钟对于处理 1100 万条记录和检查/修改每条记录中的 5 个字段来说似乎并不算太糟糕。
$ ls -l input2.csv output.csv output2.csv
-rw-r--r-- 1 cas cas 505070243 Apr 10 22:32 input2.csv
-rw-r--r-- 1 cas cas 430142246 Apr 10 22:40 output2.csv
-rw-r--r-- 1 cas cas 430142246 Apr 10 22:37 output.csv
$ cmp output.csv output2.csv
$ echo $?
0
两个输出文件是相同的,因此您的 CSV 文件看起来没有任何值 >= 100 万的数字字段。快速而肮脏的版本应该可以工作。
您的性能问题
您的 Perl 单行代码在我的系统(threadripper 1950x、64GB RAM、zfs 镜像对中的 2 个 NVME SSD*)上运行了大约 15 秒,但它没有执行任何操作 - 输出与输入相同。
我怀疑您的性能问题是由于以下一项或多项的组合造成的:
- 慢CPU
- 硬盘速度慢
- 最重要的是,内存不足
换句话说,您可能无法通过优化算法来提高性能,至少在您对系统进行一些重大升级之前是这样。如果您忽略它不起作用这一事实,您的单行代码几乎比我的快 3 倍,比 Text::CSV 版本快大约 7 倍。
一件事是可能显着提高性能(如果缓慢的磁盘 I/O 是问题的主要根源,这可能是)的方法是压缩 CSV 文件,gzip
然后将zcat
其转换为可用的单行代码。它可能会压缩到 10MB 以下 -阅读磁盘中 < 10MB 左右的数据将比磁盘快约 50 倍阅读500MB 或更多。 xz
和xzcat
类似的压缩程序也可以工作,甚至更好。
这并不意味着整个过程的运行速度会快 50 倍,只是读取文件的速度会快得多。 perl 仍然需要单独处理每一行。
* 顺便说一句,NVME 很快,但 zfs 有点慢(与 ext4 或 xfs 等简单文件系统相比)。启用 OTOH lz4 压缩,使文件读取速度再次加快。
答案2
使用csvkit
:
$ in2csv file.csv
Organization,Amount,Revenue,Balance,Desc
Congos,4233.78,3233.78,1233.78,Payment
Toyoto,590.2,390.2,190.2,Payment
lenives,5234.89,2234.89,1234.89,Payment
该in2csv
实用程序可将各种表格数据格式转换为 CSV。将完全有效的 CSV 文件转换为in2csv
会使其重新解释区域设置中的数字en_US
(默认情况下),并删除其中的,
。
这些csvkit
工具构建在用 Python 实现的 CSV 解析器之上。