我正在尝试使用下面的代码解析一个巨大的文件(大约23 MB),其中我填充了multiprocessing.manager.list,其中包含从文件中读取的所有行。在每个进程的目标例程(parse_line)中,我弹出一行并解析它以创建一个带有某些已解析属性的defaultdict对象,最后将每个对象推送到另一个multiprocessing.manager.list。
class parser(object):
def __init__(self):
self.manager = mp.Manager()
self.in_list = self.manager.list()
self.out_list = self.manager.list()
self.dict_list,self.lines, self.pcap_text = [],[],[]
self.last_timestamp = [[(999999,0)]*32]*2
self.num = Word(nums)
self.word = Word(alphas)
self.open_brace = Suppress(Literal("["))
self.close_brace = Suppress(Literal("]"))
self.colon = Literal(":")
self.stime = Combine(OneOrMore(self.num + self.colon) + self.num + Literal(".") + self.num)
self.date = OneOrMore(self.word) + self.num + self.stime
self.is_cavium = self.open_brace + (Suppress(self.word)) + self.close_brace
self.oct_id = self.open_brace + Suppress(self.word) + Suppress(Literal("=")) \
+ self.num + self.close_brace
self.core_id = self.open_brace + Suppress(self.word) + Suppress(Literal("#")) \
+ self.num + self.close_brace
self.ppm_id = self.open_brace + self.num + self.close_brace
self.oct_ts = self.open_brace + self.num + self.close_brace
self.dump = Suppress(Word(hexnums) + Literal(":")) + OneOrMore(Word(hexnums))
self.opening = Suppress(self.date) + Optional(self.is_cavium.setResultsName("cavium")) \
+ self.oct_id.setResultsName("octeon").setParseAction(lambda toks:int(toks[0])) \
+ self.core_id.setResultsName("core").setParseAction(lambda toks:int(toks[0])) \
+ Optional(self.ppm_id.setResultsName("ppm").setParseAction(lambda toks:int(toks[0])) \
+ self.oct_ts.setResultsName("timestamp").setParseAction(lambda toks:int(toks[0]))) \
+ Optional(self.dump.setResultsName("pcap"))
def parse_file(self, filepath):
self.filepath = filepath
with open(self.filepath,'r') as f:
self.lines = f.readlines()
for lineno,line in enumerate(self.lines):
self.in_list.append((lineno,line))
processes = [mp.Process(target=self.parse_line) for i in range(mp.cpu_count())]
[process.start() for process in processes]
[process.join() for process in processes]
while self.in_list:
(lineno, len) = self.in_list.pop()
print mp.current_process().name, "start"
dic = defaultdict(int)
result = self.opening.parseString(line)
self.pcap_text.append("".join(result.pcap))
if result.timestamp or result.ppm:
dic['oct'], dic['core'], dic['ppm'], dic['timestamp'] = result[0:4]
self.last_timestamp[result.octeon][result.core] = (result.ppm,result.timestamp)
else:
dic['oct'], dic['core'] = result[0:2]
dic['ppm'] = (self.last_timestamp[result.octeon][result.core])[0]
dic['ts'] = (self.last_timestamp[result.octeon][result.core])[1]
dic['line'] = lineno
self.out_list.append(dic)
但是整个过程大约需要3分钟才能完成。
我的问题是,如果有更好的方法可以让它更快?
我正在使用pyparsing模块来解析每一行,如果它有任何区别的话。
PS:改变了常规Paul McGuire的建议答案 0 :(得分:2)
不是一个很大的性能问题,但学会直接迭代文件,而不是使用readlines()。代替这段代码:
self.lines = f.readlines()
for lineno,line in enumerate(self.lines):
self.in_list.append((lineno,line))
你可以写:
self.in_list = list(enumerate(f))
隐藏的性能杀手正在使用while self.in_list: (lineno,line) = list.pop()
。每次调用pop都会从列表中删除第0个元素。不幸的是,Python的列表是作为数组实现的。要删除第0个元素,必须将1..n-1&n;第39个元素向上移动到阵列中的一个插槽。你不必
self.in_list
如果您认为随意使用self.in_list是一种节省内存的措施,那么您可以通过使用deque来避免Python列表的数组转换效率低下(来自Python提供的集合模块) 。 deque在内部实现为链表,因此从任一端推送或弹出非常快,但索引访问很慢。要使用双端队列,请替换以下行:
for lineno, line in self.in_list:
<Do something with line and line no. Parse each line and push into out_list>
使用:
self.in_list = list(enumerate(f))
然后将代码 self.in_list = deque(enumerate(f))
中的来电替换为self.in_list.pop()
。
但更有可能是性能问题的是用于处理每一行的pyparsing代码。但由于你没有发布解析器代码,我们在那里提供的帮助并不多。
要了解时间的进展情况,请尝试保留所有代码,然后注释掉self.in_list.popleft()
代码(您可能需要为for循环添加<Do something with line and line no. Parse each line and push into out_list>
语句),然后针对您的23MB文件运行。这将让您大致了解在阅读和迭代文件中花费了3分钟的时间,以及花在实际解析上的花费了多少。当你发现真正的性能问题出现在哪里时,请回到另一个问题。