我有大量的报告文件(约650个文件),需要大约320 M的硬盘,我想处理它们。每个文件中都有很多条目;我应该根据他们的内容计算并记录它们。其中一些是彼此相关的,我应该找到,记录和计算它们;匹配可能在不同的文件中。我写了一个简单的脚本来完成这项工作。我使用了python profiler,它花了大约0.3秒来运行一个单行文件的脚本,有2000行我们需要一半进行处理。但是对于整个目录来说,花了1个半小时才完成。这就是我的脚本的样子:
# imports
class Parser(object):
def __init__(self):
# load some configurations
# open some log files
# set some initial values for some variables
def parse_packet(self, tags):
# extract some values from line
def found_matched(self, packet):
# search in the related list to find matched line
def save_packet(self, packet):
# write the line in the appropriate files and increase or decrease some counters
def parse(self, file_addr):
lines = [l for index, l in enumerate(open(file_addr, 'r').readlines()) if index % 2 != 0]
for line in lines:
packet = parse_packet(line)
if found_matched(packet):
# count
self.save_packet(packet)
def process_files(self):
if not os.path.isdir(self.src_dir):
self.log('No such file or directory: ' + str(self.src_dir))
sys.exit(1)
input_dirs = os.walk(self.src_dir)
for dname in input_dirs:
file_list = dname[2]
for fname in file_list:
self.parse(os.path.join(dname[0], fname))
self.finalize_process()
def finalize_process(self):
# closing files
我想将时间减少到至少为当前执行时间的10%。也许multiprocessing
可以帮助我,或者只是当前脚本中的一些增强功能可以完成任务。无论如何,你可以帮助我吗?
修改1:
我根据@Reut Sharabani的回答更改了我的代码:
def parse(self, file_addr):
lines = [l for index, l in enumerate(open(file_addr, 'r').readlines()) if index % 2 != 0]
for line in lines:
packet = parse_packet(line)
if found_matched(packet):
# count
self.save_packet(packet)
def process_files(self):
if not os.path.isdir(self.src_dir):
self.log('No such file or directory: ' + str(self.src_dir))
sys.exit(1)
input_dirs = os.walk(self.src_dir)
for dname in input_dirs:
process_pool = multiprocessing.Pool(10)
for fname in file_list:
file_list = [os.path.join(dname[0], fname) for fname in dname[2]]
process_pool.map(self.parse, file_list)
self.finalize_process()
我还在我的课程定义之前添加了以下行,以避免PicklingError: Can't pickle <type 'instancemethod'>: attribute lookup
__builtin__.instancemethod failed
:
import copy_reg
import types
def _pickle_method(m):
if m.im_self is None:
return getattr, (m.im_class, m.im_func.func_name)
else:
return getattr, (m.im_self, m.im_func.func_name)
copy_reg.pickle(types.MethodType, _pickle_method)
我在代码中做的另一件事是不在文件处理期间保持打开日志文件;我打开并关闭它们以写入每个条目只是为了避免ValueError: I/O operation on closed file
。
现在问题是我有一些文件被多次处理。我的包也错了。我做错了什么?我应该在for循环之前放置process_pool = multiprocessing.Pool(10)
吗?考虑到我现在只有一个目录,它似乎不是问题所在。
编辑2:
我也尝试过这样使用ThreadPoolExecutor
:
with ThreadPoolExecutor(max_workers=10) as executor:
for fname in file_list:
executor.submit(self.parse, fname)
结果是正确的,但需要一个半小时才能完成。
答案 0 :(得分:3)
首先,&#34;大约650个文件,大约需要320 M&#34;不是很多。鉴于现代硬盘容易读写100 MB / s,系统的I / O性能可能不你的瓶颈(也支持&#34;它只花了大约0.3使用2000行&#34; 为一个文件运行脚本的秒数,这清楚地表明CPU限制)。但是,从Python中读取文件的确切方式可能效率不高。
此外,基于multiprocessing
的简单架构在一个通用的多核系统上运行,可以让您更快地执行分析(这里不需要涉及芹菜,不需要跨越机器边界)。 / p>
只需查看multiprocessing
,您的架构可能会涉及一个管理员进程(父进程),该进程定义任务Queue
和Pool
工作进程。管理器(或馈送器)将任务(例如文件名)放入队列,工作人员使用这些任务。完成任务后,工作人员会让经理知道,并继续消耗下一个任务。
效率很低:
lines = [l for index, l in enumerate(open(file_addr, 'r').readlines()) if index % 2 != 0]
for line in lines:
...
readlines()
在评估列表推导之前读取整个文件。只有在那之后你再次遍历所有行。因此,您通过数据迭代三次次。将所有内容组合成一个循环,这样您只需迭代一次。
答案 1 :(得分:1)
你应该在这里使用线程。如果您稍后被cpu阻止,则可以使用进程。
要解释我首先使用此命令创建了一万个文件(0.txt
... 9999.txt
),其行数相当于名称(+1):
for i in `seq 0 999`; do for j in `seq 0 $i`; do echo $i >> $i.txt; done ; done
接下来,我使用带有10个线程的ThreadPool创建了一个python脚本,以计算具有偶数值的所有文件的行:
#!/usr/bin/env python
from multiprocessing.pool import ThreadPool
import time
import sys
print "creating %s threads" % sys.argv[1]
thread_pool = ThreadPool(int(sys.argv[1]))
files = ["%d.txt" % i for i in range(1000)]
def count_even_value_lines(filename):
with open(filename, 'r') as f:
# do some processing
line_count = 0
for line in f.readlines():
if int(line.strip()) % 2 == 0:
line_count += 1
print "finished file %s" % filename
return line_count
start = time.time()
print sum(thread_pool.map(count_even_value_lines, files))
total = time.time() - start
print total
正如您所看到的,这需要时间,结果是正确的。并行处理10个文件,并且cpu足够快以处理结果。如果你想要更多,你可以考虑使用线程和进程来利用所有cpus以及不让IO阻止你。
正如评论所示,我错了,这是不 I / O被阻止,因此您可以使用多处理(cpu阻止)加快速度。因为我使用的ThreadPool与Pool具有相同的接口,所以您可以进行最少的编辑并运行相同的代码:
#!/usr/bin/env python
import multiprocessing
import time
import sys
files = ["%d.txt" % i for i in range(2000)]
# function has to be defined before pool is opened and workers are forked
def count_even_value_lines(filename):
with open(filename, 'r') as f:
# do some processing
line_count = 0
for line in f:
if int(line.strip()) % 2 == 0:
line_count += 1
return line_count
print "creating %s processes" % sys.argv[1]
process_pool = multiprocessing.Pool(int(sys.argv[1]))
start = time.time()
print sum(process_pool.map(count_even_value_lines, files))
total = time.time() - start
print total
结果:
me@EliteBook-8470p:~/Desktop/tp$ python tp.py 1
creating 1 processes
25000000
21.2642059326
me@EliteBook-8470p:~/Desktop/tp$ python tp.py 10
creating 10 processes
25000000
12.4360249043
答案 2 :(得分:1)
除了使用并行处理之外,@ Jan-PhilipGehrcke已经指出,parse
方法的效率相当低。扩展他的建议:经典变体:
def parse(self, file_addr):
with open(file_addr, 'r') as f:
line_no = 0
for line in f:
line_no += 1
if line_no % 2 != 0:
packet = parse_packet(line)
if found_matched(packet):
# count
self.save_packet(packet)
或者使用你的风格(假设你使用python 3):
def parse(self, file_addr):
with open(file_addr, 'r') as f:
filtered = (l for index,l in enumerate(f) if index % 2 != 0)
for line in filtered:
# and so on
这里需要注意的是迭代器的使用,构建过滤列表(实际上不是列表!!)的所有操作都在操作并返回迭代器,这意味着整个文件都没有被加载到一个列表。