Python多处理文本提取性能问题与Perl等价物
将此标记为已回答,并围绕速度问题真正出现的位置开始了一个更简单的主题 感谢迄今为止的所有评论,非常有用 我有大约4000万个XML文件(不是均匀地)分布在大约60K个子目录中,其结构基于10位数的分割,因此: 12/34/56/78/90/files.xml 我有一个perl脚本,它针对文件运行,将单个字段的值提取出来,并打印值和文件名。Perl脚本封装在一个bash脚本中,该脚本在深度2的所有目录列表中运行最多12个并行实例,然后遍历每个目录,并在找到文件时在底层处理这些文件 从多次运行中取出磁盘缓存一次unix进程将返回大约:Python多处理文本提取性能问题与Perl等价物,python,perl,python-multiprocessing,Python,Perl,Python Multiprocessing,将此标记为已回答,并围绕速度问题真正出现的位置开始了一个更简单的主题 感谢迄今为止的所有评论,非常有用 我有大约4000万个XML文件(不是均匀地)分布在大约60K个子目录中,其结构基于10位数的分割,因此: 12/34/56/78/90/files.xml 我有一个perl脚本,它针对文件运行,将单个字段的值提取出来,并打印值和文件名。Perl脚本封装在一个bash脚本中,该脚本在深度2的所有目录列表中运行最多12个并行实例,然后遍历每个目录,并在找到文件时在底层处理这些文件 从多次运行中取
real 37m47.993s
user 49m50.143s
sys 54m57.570s
我想将其迁移到python脚本(作为一个学习练习和测试),因此(在大量阅读各种python方法之后)创建了以下内容:
如果我在单个线程中针对一个小子集(最终得到完全缓存)运行python和perl脚本,因此没有磁盘io(根据iotop),那么脚本运行的时间几乎相同
到目前为止,我能想到的唯一结论是,python脚本中的文件io比perl脚本中的文件io效率低得多,因为似乎是io导致了这个问题
所以希望这是足够的背景知识,我的问题是我是否做了一些愚蠢的事情,或者在我没有想法的情况下错过了一个技巧,但我不敢相信io会在处理时间上造成如此大的差异
感谢您的指点,如有需要,我们将提供更多信息
谢谢
硅
以下是Perl脚本的参考:
use File::Find;
my $cwd = `pwd`;
chomp $cwd;
find( \&hasxml, shift );
sub hasxml {
if (-d) {
my @files = <$_/*.xml>;
if ( scalar(@files) > 0 ) {
process("$cwd/${File::Find::dir}/$_");
}
}
}
sub process {
my $dir = shift;
my @files = <$dir/*.xml>;
foreach my $file (@files) {
my $fh;
open( $fh, "< $file" ) or die "Could not read file <$file>";
my $contents = do { local $/; <$fh> };
close($fh);
my ($id) = $contents =~ /<field name="FIELDNAME">([^<]+)<\/field>/s;
print "$file\t<$id>\n";
}
}
使用File::Find;
我的$cwd=`pwd`;
chomp$cwd;
查找(\&hasxml,shift);
子hasxml{
如果(-d){
我的@files=;
如果(标量(@files)>0){
进程(“$cwd/${File::Find::dir}/$\”;
}
}
}
子过程{
我的$dir=shift;
我的@files=;
foreach my$文件(@files){
我的$fh;
打开($fh,“<$file”)或死亡“无法读取文件”;
my$contents=do{local$/;};
收盘价($fh);
我的($id)=$contents=~/([^根据XML文件的结构,您可以通过使用来节省一些时间。目前,您正在读取整个文件,即使您只对一个条目感兴趣。如果您的数据往往出现在文件顶部附近,您可以将文件映射到内存中,而不是实际读取它,请执行regex搜索事实上,你已经是一样的了,就这样吧
以下是两种方法的比较:
我有一个名为“tmp_large.txt”的文本文件,其中有1000000行。每行都有小写字母。在文件中间的一行中,我将字母“m”替换为“x”,我正在搜索该字符串:
import re
import mmap
from timeit import timeit
from datetime import timedelta
c_regex = re.compile('defghijklxnopqrstuvwx')
def read_file():
with open('tmp_large.txt', 'r') as fi:
f = fi.read()
match = c_regex.search(f)
def mmap_file():
with open('tmp_large.txt', 'r+b') as fi: # must open as binary for mmap
mm = mmap.mmap(fi.fileno(), 0)
match = c_regex.search(mm)
mm.close()
t1 = timedelta(seconds=timeit(read_file, setup='gc.enable()', number=1))
t2 = timedelta(seconds=timeit(mmap_file, setup='gc.enable()', number=1))
print(t1)
print(t2)
此场景生成以下输出:
0:00:00.036021
0:00:00.028974
我们看到执行时间节省了不到三分之一的时间。但是,如果我将要查找的字符串放在输入文件的顶部,我们会看到以下结果:
0:00:00.009327
0:00:00.000338
显然,这两种方法都更快,但对于内存映射方法来说,时间节省更为显著
由于我不知道数据的结构或文件有多大,您可能会从中看到不太明显的结果。但只要您要查找的数据不在目标文件的末尾,您可能会看到内存映射文件的一点改进,因为这样可以避免将您实际上不需要的数据带入内存我已经用完了
作为补充说明,我还尝试在文件中迭代行,直到找到一行与正则表达式匹配的行,但是速度太慢,无法在这里包含。此外,在我的示例中,我确实确认正则表达式确实匹配,但为了简洁起见,我删除了打印代码和结果
正如评论中所建议的,使用迭代器并用类似的内容替换映射也可能有助于加快速度,因为它们都有助于减少内存占用:
processPool = Pool(12)
for dir_or_file in glob.iglob('??/??/??/??/??'):
if os.path.isdir(dir_or_file):
processPool.apply_async(processDir, (dir_or_file,))
processPool.close()
processPool.join()
这种方法还允许您的子流程在识别其余文件的同时开始处理第一个文件
其他一些代码注释供您参考:
您不需要在正则表达式上使用re.S标志,因为在正则表达式模式中实际上没有任何“.”
除非您有令人信服的理由不这样做,否则您应该使用with open()
以与打开输入文件相同的方式打开输出文件,以防止出现异常时出现错误的打开文件描述符
当您计算数据文件和文件集时,考虑使用,而不是手动添加路径分隔符。从长远来看,它将不会容易出错。
你不需要你的global regex
行。你总是可以在没有它的情况下读取和调用全局对象的方法,就像我的示例一样。你只需要在修改全局对象时才需要它
以防您不知道,默认情况下,将只启动与CPU核心数量相同的工作进程。如果您已经知道,请忽略此注释。为池指定12个进程对我来说似乎有点奇怪
根据XML文件的结构,您可以通过使用来节省一些时间。目前,您正在读取整个文件,即使您只对一个条目感兴趣。如果您的数据往往出现在文件顶部附近,您可以将文件映射到内存中,而不是实际读取它,请执行regex search exac这和你已经是一样的了,就这样吧
以下是两种方法的比较:
我有一个名为“tmp_large.txt”的文本文件,其中有1000000行,每行都有字母
import re
import mmap
from timeit import timeit
from datetime import timedelta
c_regex = re.compile('defghijklxnopqrstuvwx')
def read_file():
with open('tmp_large.txt', 'r') as fi:
f = fi.read()
match = c_regex.search(f)
def mmap_file():
with open('tmp_large.txt', 'r+b') as fi: # must open as binary for mmap
mm = mmap.mmap(fi.fileno(), 0)
match = c_regex.search(mm)
mm.close()
t1 = timedelta(seconds=timeit(read_file, setup='gc.enable()', number=1))
t2 = timedelta(seconds=timeit(mmap_file, setup='gc.enable()', number=1))
print(t1)
print(t2)
processPool = Pool(12)
for dir_or_file in glob.iglob('??/??/??/??/??'):
if os.path.isdir(dir_or_file):
processPool.apply_async(processDir, (dir_or_file,))
processPool.close()
processPool.join()
import glob, sys, re
def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
return [int(text) if text.isdigit() else text.lower()
for text in re.split(_nsre, s)]
for file in sorted(glob.iglob(sys.argv[1] + '/*.xml'), key=natural_sort_key):
with open(file) as x:
f = x.read()
$ sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'
$ /usr/bin/time perl slurp.pl 1
1.21user 2.17system 0:12.70elapsed 26%CPU (0avgtext+0avgdata 9140maxresident)k
1234192inputs+0outputs (22major+2466minor)pagefaults 0swaps
$ sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'
$ /usr/bin/time python slurp.py 1
2.88user 6.13system 4:48.00elapsed 3%CPU (0avgtext+0avgdata 8020maxresident)k
1236304inputs+0outputs (35major+52252minor)pagefaults 0swaps
$ sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'
$ /usr/bin/time python slurpNatural.py 1
1.25user 2.82system 0:10.70elapsed 38%CPU (0avgtext+0avgdata 22408maxresident)k
1237360inputs+0outputs (35major+56531minor)pagefaults 0swaps