我在Python 2.7.6中解析非常大的文本文件(30GB +)。为了加快这一过程,我将文件拆分为块并使用多处理库将它们分解为子进程。为此,我在主进程中迭代文件,记录我要分割输入文件的字节位置,并将这些字节位置传递给子进程,然后打开输入文件并使用{{1 }}。但是,我发现读入的块似乎比file.readlines(chunk_size)
参数大得多(4x)。
为什么不注意尺寸提示?
以下代码演示了我的问题:
sizehint
我使用大约23.3KB的输入文件(test.txt)运行此代码并生成以下输出:
块#1:大小:2052开始0实时开始:0停止2052实时停止:8193
块#2:大小:2051开始2052实时开始:2052停止4103实时停止:10248
块#3:大小:2050开始4103实时开始:4103停止6153实际停止:12298
块#4:大小:2050开始6153实时开始:6153停止8203实时停止:14348
块#5:大小:2050开始8203实时开始:8203停止10253实际停止:16398
块#6:尺寸:2050开始10253实时开始:10253停止12303实际停止:18448
块#7:大小:2050开始12303实时开始:12303停止14353实际停止:20498
块#8:大小:2050开始14353实时开始:14353停止16403实际停止:22548
块#9:尺寸:2050开始16403实时开始:16403停止18453实际停止:23893
块#10:大小:2050开始18453实时开始:18453停止20503实际停止:23893
块#11:尺寸:2050开始20503实时开始:20503停止22553实际停止:23893
块#12:大小:2048开始22553实时开始:22553停止24601实时停止:23893
报告的每个块大小约为2KB,所有的开始/停止位置都按照应有的方式排列,import sys
# set test chunk size to 2KB
chunk_size = 1024 * 2
count = 0
chunk_start = 0
chunk_list = []
fi = open('test.txt', 'r')
while True:
# increment chunk counter
count += 1
# calculate new chunk end, advance file pointer
chunk_end = chunk_start + chunk_size
fi.seek(chunk_end)
# advance file pointer to end of current line so chunks don't have broken
# lines
fi.readline()
chunk_end = fi.tell()
# record chunk start and stop positions, chunk number
chunk_list.append((chunk_start, chunk_end, count))
# advance start to current end
chunk_start = chunk_end
# read a line to confirm we're not past the end of the file
line = fi.readline()
if not line:
break
# reset file pointer from last line read
fi.seek(chunk_end, 0)
fi.close()
# This code represents the action taken by subprocesses, but each subprocess
# receives one chunk instead of iterating the list of chunks itself.
with open('test.txt', 'r', 0) as fi:
# iterate over chunks
for chunk in chunk_list:
chunk_start, chunk_end, chunk_num = chunk
# advance file pointer to chunk start
fi.seek(chunk_start, 0)
# print some notes and read in the chunk
sys.stdout.write("Chunk #{0}: Size: {1} Start {2} Real Start: {3} Stop {4} "
.format(chunk_num, chunk_end-chunk_start, chunk_start, fi.tell(), chunk_end))
chunk = fi.readlines(chunk_end - chunk_start)
print("Real Stop: {0}".format(fi.tell()))
# write the chunk out to a file for examination
with open('test_chunk{0}'.format(chunk_num), 'w') as fo:
fo.writelines(chunk)
报告的实际文件位置似乎是正确的,所以我是相当肯定我的分块算法是好的。但是,实际停止位置显示fi.tell()
读取的内容远远超过大小提示。此外,输出文件#1 - #8是8.0KB,远大于提示大小。
即使我尝试仅在线端打破块也是错误的,readlines()
仍然不应该读取超过2KB +一行。文件#9 - #12变得越来越小,这是有道理的,因为块起始点越来越接近文件的末尾,并且readlines()
不会读取超过文件的末尾。
readlines()
来获取文件大小。见my related question。fi.tell()
作为分块循环的停止条件,并且readlines似乎与该方法一样正常。答案 0 :(得分:2)
readlines
文档提到的缓冲区与open
调用的第三个参数控制的缓冲无关。缓冲区为this buffer in file_readlines
:
static PyObject *
file_readlines(PyFileObject *f, PyObject *args)
{
long sizehint = 0;
PyObject *list = NULL;
PyObject *line;
char small_buffer[SMALLCHUNK];
之前定义了SMALLCHUNK
:
#if BUFSIZ < 8192
#define SMALLCHUNK 8192
#else
#define SMALLCHUNK BUFSIZ
#endif
我不知道BUFSIZ
来自何处,但看起来您正在获得#define SMALLCHUNK 8192
案例。在任何情况下,readlines
都不会使用小于8 KiB的缓冲区,所以你应该让你的块大于那个。
答案 1 :(得分:0)
这并不能回答你的问题,但也许会有所帮助......
我觉得可能有更好的方法 chunk 你的文件,这会绕过你当前的问题。只是一个想法,但由于文件可以迭代,这样的工作会起作用吗?
import bzip2
import gzip
from multiprocessing import Pool, cpu_count
def chunker(filepath):
"""define and yield chunks"""
if filepath.endswith(".bz"):
read_open = bzip2.open
elif filepath.endswith(".gz"):
read_open = gzip.open
with read_open(filepath) as in_f:
delim = "something"
chunk = []
for line in in_f:
if delim not in line:
chunk.append(line)
else:
current, next_ = line.split(delim)
chunk.append(current)
yield chunk
chunk = [next_]
if chunk:
yield chunk
def process_chunk(chunk):
# do magic
return
if __name__ == '__main__':
filepath = ""
chunk_iter = chunker(filepath)
pool = Pool(processes=cpu_count() - 1)
for result in pool.imap(process_chunk, chunk_iter , chunksize=1)
print result
或者如果您已经使用1-pass读取并生成块列表,为什么不在读取时将单独的块写为单独的文件(如果您有磁盘空间)。然后,您可以为工作池提供要处理的文件路径列表。
或者,如果您的工作人员足够快以处理块并且您有内存,那么您可以在阅读时将整个块传递给Queue。工人可以从队列中拉出块。