有效地递归文件目录,同时最大限度地减少Python中的内存使用量

时间:2014-05-30 13:44:06

标签: python multiprocessing directory-structure

我有大量文件,我想通过这些文件来执行md5校验和。

其中许多文件存储在多个物理磁盘上,但都安装在同一目录中:

/mnt/drive1/dir1/file.jpg
/mnt/drive2/dir1/file2.jpg    

如何在不将整个目录和文件结构加载到内存的情况下递归/ mnt?

有多种方法可以使用多个线程吗?可能没有必要使用多个线程/进程递归目录,但文件操作可能是CPU密集型的,这将从多个CPU核心中受益。

提前致谢。

4 个答案:

答案 0 :(得分:2)

为什么不使用简单的os.walk?它没有占用任何大量的记忆。

import os
for root, dirs, files in os.walk('/mnt'):
    for name in files:
        print os.path.join(root, name)

答案 1 :(得分:2)

稍微扩展我的注释,此代码创建一个进程池(在命令行上给出的大小),它打开当前目录下的每个文件,将其拉链50次,并计算其CRC。然后它将所有CRC共同扫描并打印出来。

import multiprocessing
import os
import sys
import zlib

NUM_PROCS = int(sys.argv[1])

def processFile(filepath):
    infile = open(filepath, 'r')
    contents = infile.read()
    for i in xrange(50):
        contents = zlib.compress(contents)
    return zlib.crc32(contents)

def generateFilepaths():
    for (dirpath, dirnames, filenames) in os.walk('.'):
        for filename in filenames:
            filepath = os.path.join(dirpath, filename)
            yield filepath

if __name__ == '__main__':
    pool = multiprocessing.Pool(NUM_PROCS)

    fullCrc = 0
    for crc in pool.imap_unordered(processFile, generateFilepaths()):
        fullCrc ^= crc

    print fullCrc

请注意,如果不对每个文件执行如此荒谬的工作量,此程序仍将完全受IO限制。因此,线程很可能会以极低的速度获得。

答案 2 :(得分:1)

import multiprocessing
import os.path
import hashlib
import sys


VALID_EXTENSIONS = ('.JPG', '.GIF', '.JPEG')
MAX_FILE_SZ = 1000000


def md5_file(fname):
    try:
        with open(fname) as fo:
            m = hashlib.md5()
            chunk_sz = m.block_size * 128
            data = fo.read(chunk_sz)
            while data:
                m.update(data)
                data = fo.read(chunk_sz)
        md5_file.queue.put((fname, m.hexdigest()))
    except IOError:
        md5_file.queue.put((fname, None))


def is_valid_file(fname):
    ext = os.path.splitext(fname)[1].upper()
    fsz = os.path.getsize(fname)
    return ext in VALID_EXTENSIONS and fsz <= MAX_FILE_SZ


def init(queue):
    md5_file.queue = queue


def main():
    # Holds tuple (fname, md5sum) / md5sum will be none if an IOError occurs
    queue = multiprocessing.Queue()
    pool = multiprocessing.Pool(None, init, [queue])

    for dirpath, dirnames, filenames in os.walk(sys.argv[1]):
        # Convert filenames to full paths...
        full_path_fnames = map(lambda fn: os.path.join(dirpath, fn), 
                               filenames)
        full_path_fnames = filter(is_valid_file, full_path_fnames)
        pool.map(md5_file, full_path_fnames)

    # Dump the queue
    while not queue.empty():
        print queue.get()
    return 0

if __name__ == '__main__':
    sys.exit(main())

可能不是防弹,但它对我有用。你可能想要调整它以提供一些关于它正在做什么的反馈。

由于某些奇怪的原因,您无法共享全局队列。所以,我不得不使用池的initializer函数。我不确定为什么会这样。

只需将根目录作为唯一参数传递给它,它将在完成后转储md5总和。

答案 3 :(得分:0)

不确定是否有一个普遍正确的答案,但也许你想从一些简单的,基准的开始,看看有什么瓶颈。

选项1

使用find /mnt -type f生成文件列表并将其传递给脚本。这可以使用多个工作线程(multiprocessing)完美地并行化,但您可能希望根据物理设备拆分列表,以便每个线程处理每个线程。如果连接的存储空间很慢,这可能很重要。

选项2

生成顶级目录列表(例如,max-depth 1或2),让线程分别在每个分区上运行。这可以通过Linux'find -typde d和/或Python os.walk()完成。


只要您不确切知道它有多糟糕,我就不会费心加载到内存中。也就是说,在你做基准之前。另外,我认为os.walk或文件列表不会成为严重的内存问题。如果我错了,请纠正我。