簡體   English   中英

在 python 中批處理非常大的文本文件

[英]Batching very large text file in python

我正在嘗試將一個非常大的文本文件(大約 150 GB)批處理成幾個較小的文本文件(大約 10 GB)。

我的一般流程是:

# iterate over file one line at a time
# accumulate batch as string 
--> # given a certain count that correlates to the size of my current accumulated batch and when that size is met: (this is where I am unsure)
        # write to file

# accumulate size count

我有一個粗略的指標來計算何時進行批處理(當所需的批處理大小時),但我不太清楚我應該如何計算給定批處理寫入磁盤的頻率。 例如,如果我的批處理大小是 10 GB,我假設我需要迭代寫入而不是在 memory 中保存整個 10 GB 批處理。 我顯然不想寫太多,因為這可能非常昂貴。

您是否有任何粗略的計算或技巧,您喜歡用來確定何時為諸如此類的任務寫入磁盤,例如大小與 memory 之類的?

我使用稍微修改過的版本來解析 250GB json,我選擇需要多少個較小的文件number_of_slices ,然后找到對文件進行切片的位置(我總是尋找行尾)。 最后我用file.seekfile.read(chunk)切片文件

import os
import mmap


FULL_PATH_TO_FILE = 'full_path_to_a_big_file'
OUTPUT_PATH = 'full_path_to_a_output_dir' # where sliced files will be generated


def next_newline_finder(mmapf):
    def nl_find(mmapf):
        while 1:
            current = hex(mmapf.read_byte())
            if hex(ord('\n')) == current:  # or whatever line-end symbol
                return(mmapf.tell())
    return nl_find(mmapf)


# find positions where to slice a file
file_info = os.stat(FULL_PATH_TO_FILE)
file_size = file_info.st_size
positions_for_file_slice = [0]
number_of_slices = 15  # say u want slice the big file to 15 smaller files
size_per_slice = file_size//number_of_slices

with open(FULL_PATH_TO_FILE, "r+b") as f:
    mmapf = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
    slice_counter = 1
    while slice_counter < number_of_slices:
        pos = size_per_slice*slice_counter
        mmapf.seek(pos)
        newline_pos = next_newline_finder(mmapf)
        positions_for_file_slice.append(newline_pos)
        slice_counter += 1

# create ranges for found positions (from, to)
positions_for_file_slice = [(pos, positions_for_file_slice[i+1]) if i < (len(positions_for_file_slice)-1) else (
    positions_for_file_slice[i], file_size) for i, pos in enumerate(positions_for_file_slice)]


# do actual slice of a file
with open(FULL_PATH_TO_FILE, "rb") as f:
    for i, position_pair in enumerate(positions_for_file_slice):
        read_from, read_to = position_pair
        f.seek(read_from)
        chunk = f.read(read_to-read_from)
        with open(os.path.join(OUTPUT_PATH, f'dummyfile{i}.json'), 'wb') as chunk_file:
            chunk_file.write(chunk)

假設您的大文件是簡單的非結構化文本,即這對於像 JSON 這樣的結構化文本沒有好處,這是讀取每一行的替代方法:讀取輸入文件的大二進制位,直到您的塊大小,然后讀取幾行,關閉當前 output 文件並繼續下一個。

我使用與我的代碼相同的塊大小調整的@tdelaney 代碼將其與逐行進行比較 - 該代碼需要 250 秒才能將 12GiB 輸入文件拆分為 6x2GiB 塊,而這需要大約 50 秒,所以可能快五倍,看起來像我的 SSD 上的 I/O 綁定運行 >200MiB/s 讀寫,其中逐行運行 40-50MiB/s 讀寫。

我關閉了緩沖,因為沒有太多意義。 咬合的大小和緩沖設置可能是可調的以提高性能,沒有嘗試任何其他設置,因為對我來說它似乎是 I/O 綁定的。

import time

outfile_template = "outfile-{}.txt"
infile_name = "large.text"
chunksize = 2_000_000_000
MEB = 2**20   # mebibyte
bitesize = 4_000_000 # the size of the reads (and writes) working up to chunksize

count = 0

starttime = time.perf_counter()

infile = open(infile_name, "rb", buffering=0)
outfile = open(outfile_template.format(count), "wb", buffering=0)

while True:
    byteswritten = 0
    while byteswritten < chunksize:
        bite = infile.read(bitesize)
        # check for EOF
        if not bite:
            break
        outfile.write(bite)
        byteswritten += len(bite)
    # check for EOF
    if not bite:
        break
    for i in range(2):
        l = infile.readline()
        # check for EOF
        if not l:
            break
        outfile.write(l)
    # check for EOF
    if not l:
        break
    outfile.close()
    count += 1
    print( count )
    outfile = open(outfile_template.format(count), "wb", buffering=0)

outfile.close()
infile.close()

endtime = time.perf_counter()

elapsed = endtime-starttime

print( f"Elapsed= {elapsed}" )

注意我沒有詳盡地測試過它不會丟失數據,盡管沒有證據表明它會丟失任何你應該自己驗證的東西。

通過檢查何時在塊的末尾以查看還有多少數據要讀取來增加一些穩健性可能很有用,因此您最終不會得到最后一個 output 文件的長度為 0(或短於比特大小)

HTH 谷倉

這是逐行寫入的示例。 它以二進制模式打開,以避免行解碼步驟,這需要一些時間,但可能會扭曲字符計數。 例如,utf-8 編碼可以使用磁盤上的多個字節來表示單個 python 字符。

4 Meg 是對緩沖的猜測。 這個想法是讓操作系統一次讀取更多文件,減少查找時間。 這是否有效或使用的最佳數字是有爭議的 - 並且對於不同的操作系統會有所不同。 我發現 4 兆有所不同...但那是幾年前的事了,情況發生了變化。

outfile_template = "outfile-{}.txt"
infile_name = "infile.txt"
chunksize = 10_000_000_000
MEB = 2**20   # mebibyte

count = 0
byteswritten = 0
infile = open(infile_name, "rb", buffering=4*MEB)
outfile = open(outfile_template.format(count), "wb", buffering=4*MEB)

try:
    for line in infile:
        if byteswritten > chunksize:
            outfile.close()
            byteswritten = 0
            count += 1
            outfile = open(outfile_template.format(count), "wb", buffering=4*MEB)
        outfile.write(line)
        byteswritten += len(line)
finally:
    infile.close()
    outfile.close()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM