繁体   English   中英

在python中处理大量文件

[英]Processing a huge amount of files in python

我有大量的报告文件(大约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)

结果是正确的,但需要一个半小时才能完成。

首先,“大约650个大约320M的文件”并不是很多。 鉴于现代硬盘可以轻松读写100 MB / s,系统的I / O性能可能不是您的瓶颈(也支持“只需0.3秒即可为一个单独的文件运行2000行” ,这清楚地表明CPU限制)。 但是,从Python中读取文件的确切方式可能效率不高。

此外,在一个通用的多核系统上运行的简单的基于multiprocessing的体系结构将允许您更快地执行分析(这里不需要涉及芹菜,不需要跨越机器边界)。

多处理架构

只需看看multiprocessing ,您的架构可能会涉及一个管理器进程(父进程),它定义了一个任务Queue和一个工作进程Pool 管理器(或馈送器)将任务(例如文件名)放入队列,工作人员使用这些任务。 完成任务后,工作人员让经理知道,并继续消耗下一个任务。

文件处理方法

这是非常低效的:

    lines = [l for index, l in enumerate(open(file_addr, 'r').readlines()) if index % 2 != 0]
    for line in lines:
        ...

readlines()在评估列表readlines()之前读取整个文件。 只有在那之后你再次遍历所有行。 因此,您在数据中迭代三次 将所有内容组合到一个循环中,这样您只需迭代一次。

你应该在这里使用线程。 如果您稍后被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阻止)来加速它。 因为我使用了与Pool具有相同接口的ThreadPool,所以可以进行最少的编辑并运行相同的代码:

#!/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

除了使用并行处理之外,@ 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

这里要注意的是使用迭代器,所有操作来构建过滤列表(实际上不是列表!!)操作并返回迭代器,这意味着整个文件都不会被加载到列表中。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM