并行磁盘I/O
我有几个日志文件想要读取。为了简单起见,我们假设日志文件的处理方式如下:
def process(infilepath):
answer = 0
with open (infilepath) as infile:
for line in infile:
if line.startswith(someStr):
answer += 1
return answer
因为我有很多日志文件,所以我想用多进程来解决这个问题(我第一次犯的错误是:我应该使用多线程;有人能告诉我为什么吗)
在这个过程中,我意识到任何形式的并行处理在这里可能都没什么用,因为我的硬盘只有一个读写头,所以一次只能读取一个文件。实际上,根据这个推理,由于可能会同时请求来自不同文件的行,读写头可能需要频繁移动,这样一来,多进程的方式可能比串行处理还要慢。因此,我决定回到单进程来读取我的日志文件。
有趣的是,我注意到在处理小文件(小于等于40KB)时,确实有提速,而在处理大文件(大于等于445MB)时,才发现了预期的速度下降。
这让我相信,Python 可能是以块的方式读取文件,每次读取的大小超过我请求的一行。
问题1:那么,底层的文件读取机制是什么样的?
问题2:从传统硬盘中读取文件的最佳优化方法是什么?
技术规格:
- python3.3
- 5400转传统硬盘
- Mac OSX 10.9.2(Mavericks)
3 个回答
这里有一个使用内存映射文件的例子
import mmap
with open("hello.txt", "r+b") as f:
mapf = mmap.mmap(f.fileno(), 0)
print(mapf.readline())
mapf.close()
enter code here
另一个想法是对你的代码进行性能分析。
try:
import cProfile as profile
except ImportError:
import profile
profile.run("process()")
观察到的行为是由以下两个原因造成的:
- 缓冲输入输出(BufferedIO)
- 一种调度算法,它决定了硬盘驱动器(HDD)中读取所需扇区的顺序
缓冲输入输出(BufferedIO)
根据操作系统和读取块的大小,有可能整个文件可以放进一个块里,这样就可以通过一个读取命令一次性读取。这就是为什么小文件读取起来更容易的原因。
调度算法
对于较大的文件(文件大小大于读取块大小),必须分块读取,也就是按块大小
来读取。因此,当同时请求读取多个文件时(因为多进程的原因),读取头需要移动到硬盘的不同扇区(也就是文件所在的位置)。这种重复的移动会导致两个问题:
- 增加对同一个文件的连续读取之间的时间
- 影响读取扇区的预测,因为一个文件可能跨越多个扇区
对同一个文件的连续读取之间的时间很重要,如果对一部分行的计算在读取头能够提供同一个文件的下一部分行之前就完成了,处理过程就会等待,直到下一部分行变得可用。这是导致速度变慢的一个原因。
影响读取预测器的效果与影响分支预测器的效果类似,都是不好的。
由于这两个问题的共同影响,同时处理多个大文件的速度会比串行处理要慢。当然,当处理blockSize
行的任务完成在numProcesses * blockSize
行可以从硬盘读取出来之前,这种情况就更明显了。