用于解析大文件的多处理和共享多处理管理器列表

时间:2015-03-24 21:23:05

标签: multiprocessing python-2.6 pyparsing

我正在尝试使用下面的代码解析一个巨大的文件(大约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的建议

1 个答案:

答案 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分钟的时间,以及花在实际解析上的花费了多少。当你发现真正的性能问题出现在哪里时,请回到另一个问题。