Python 为什么同时读取多个文件比顺序读取慢?

Python 为什么同时读取多个文件比顺序读取慢?,python,performance,python-2.7,io,multiprocessing,Python,Performance,Python 2.7,Io,Multiprocessing,我试图解析目录中的许多文件,但是使用多处理会减慢我的程序 # Calling my parsing function from Client. L = getParsedFiles('/home/tony/Lab/slicedFiles') <--- 1000 .txt files found here. combined ~100MB 我已经写了这段代码: from multi

我试图解析目录中的许多文件,但是使用多处理会减慢我的程序

# Calling my parsing function from Client.
L = getParsedFiles('/home/tony/Lab/slicedFiles') <--- 1000 .txt files found here.
                                                       combined ~100MB
我已经写了这段代码:

from multiprocessing import Pool
from api.ttypes import *

import gc
import os

def _parse(pathToFile):
    myList = []
    with open(pathToFile) as f:
        for line in f:
            s = line.split()
            x, y = [int(v) for v in s]
            obj = CoresetPoint(x, y)
            gc.disable()
            myList.append(obj)
            gc.enable()
    return Points(myList)

def getParsedFiles(pathToFile):
    myList = []
    p = Pool(2)
    for filename in os.listdir(pathToFile):
        if filename.endswith(".txt"):
            myList.append(filename)
    return p.map(_pars, , myList)
我遵循这个示例,将所有以
.txt
结尾的文件名放在一个列表中,然后创建池,并将它们映射到我的函数。然后我想返回一个对象列表。每个对象都保存一个文件的解析数据。然而,令我惊讶的是,我得到了以下结果:

#Pool 32  ---> ~162(s)
#Pool 16 ---> ~150(s)
#Pool 12 ---> ~142(s)
#Pool 2 ---> ~130(s)
图形:

机器规格:

62.8Gib内存
英特尔®核心™ i7-6850K CPU@3.60GHz×12
我错过了什么?
提前谢谢

看起来你是:

在计算机科学中,I/O界限是指完成计算所需的时间主要由等待输入/输出操作完成的时间决定的一种情况。这与CPU受限的任务相反。当请求数据的速率比消耗数据的速率慢,或者换句话说,请求数据的时间比处理数据的时间长时,就会出现这种情况

当子进程可用时,您可能需要让主线程进行读取并将数据添加到池中。这与使用
map
不同

当您一次处理一行,并且输入被拆分时,您可以使用迭代多个文件的行,并映射到函数处理行而不是文件:

一次只传递一行可能太慢,所以我们可以让map传递数据块,并进行调整,直到找到一个最佳点。我们的函数解析行块:

def _parse_coreset_points(lines):
    return Points([_parse_coreset_point(line) for line in lines])

def _parse_coreset_point(line):
    s = line.split()
    x, y = [int(v) for v in s]
    return CoresetPoint(x, y)
我们的主要职能是:

import fileinput

def getParsedFiles(directory):
    pool = Pool(2)

    txts = [filename for filename in os.listdir(directory):
            if filename.endswith(".txt")]

    return pool.imap(_parse_coreset_points, fileinput.input(txts), chunksize=100)

一般来说,同时从不同线程读取同一物理(旋转)硬盘绝对不是一个好主意,因为每个开关都会导致额外的延迟,大约10毫秒来定位硬盘的读取头(在SSD上可能不同)

正如@peter wood已经说过的,最好让一个线程读取数据,让其他线程处理该数据

另外,为了真正测试差异,我认为应该使用一些更大的文件进行测试。例如:当前的硬盘应该能够读取大约100MB/秒的数据。因此,一次性读取100kB文件的数据需要1ms,而将读取头定位到该文件的开头则需要10ms

另一方面,看看您的数字(假设这些数字是针对单个循环的),很难相信I/O绑定是这里唯一的问题。总数据量为100MB,从磁盘读取数据需要1秒,外加一些开销,但您的程序需要130秒。我不知道这个数字是磁盘上的冷文件,还是操作系统已经缓存了数据的多个测试的平均值(使用62 GB或RAM,所有这些数据都应该在第二次缓存)-看到这两个数字会很有趣

所以肯定还有别的东西。让我们仔细看看你的循环:

for line in f:
    s = line.split()
    x, y = [int(v) for v in s]
    obj = CoresetPoint(x, y)
    gc.disable()
    myList.append(obj)
    gc.enable()
虽然我不懂Python,但我猜,
gc
调用是这里的问题。从磁盘读取的每一行都会调用它们。我不知道这些调用的开销有多大(或者如果
gc.enable()
触发垃圾收集会怎么样),也不知道为什么只在
append(obj)
附近需要它们,但可能还有其他问题,因为这是多线程:

假设
gc
对象是全局对象(即非线程本地对象),则可以有如下内容:

thread 1 : gc.disable()
# switch to thread 2
thread 2 : gc.disable()
thread 2 : myList.append(obj)
thread 2 : gc.enable()
# gc now enabled!
# switch back to thread 1 (or one of the other threads)
thread 1 : myList.append(obj)
thread 1 : gc.enable()
def disable()
    lock()  # all other threads are blocked for gc calls now
    alter internal data
    unlock()

如果线程数很好,看起来你是。但是文件在那里,发送到函数的列表包含所有文件名。程序花了0.1秒才将所有文件名放入列表中。I/O在这里的作用是什么?池不会在继续之前等待名称被提取并放入列表。您正在从文件中读取。我不知道您的磁盘是如何工作的,但我想您一次只能读取一个文件。请参阅@TonyTannous,默认答案可能是yes。可能是SSD不同,或者您的操作系统可能将多个驱动器隐藏在一个统一的文件系统后面,您可以并行访问它们。但是,如果只有一个机械驱动器,顺序驱动器是最快的。如果您的数据实际上是基于行的,并且被分割到多个文件中,那么您可能一次只做一行。将更新答案。
gc.disable()和
gc.enable()`是用于禁用\启用垃圾收集器的命令。当附加到一个巨大的列表时,它会随着列表变大而变慢,因此解决方案是禁用然后启用垃圾收集器。我不知道如果你说要花1分钟,为什么要花3分钟。也许将其转换为数据对象,然后附加到列表中并不便宜+从不同的模块调用它。但谢谢你的回答,第一部分很棒+1@TonyTannous是的,但是由于多线程,会有冲突/阻塞。在调用
getParsedFiles()
之前,可以尝试禁用一次。即使在每个循环之前和之后都无法工作,因为当第一个线程仍在循环中时,另一个线程可以调用enable。@Tony注意,从磁盘读取一个100MB文件应该需要一(或两)秒,而不是几分钟:)我将在明天回到实验室后再试。一秒半!然后我不知道为什么我的简单程序需要3分钟。。。我使用的是
thrift
框架,我用thrift类型定义类型,所以这可能也是一个原因。
def disable()
    lock()  # all other threads are blocked for gc calls now
    alter internal data
    unlock()