簡體   English   中英

處理大文件的最快方法?

[英]Fastest way to process a large file?

我有多個3 GB制表符分隔文件。 每個文件中有2000萬行。 所有行必須獨立處理,任何兩行之間沒有關系。 我的問題是,什么會更快A.使用以下方式逐行閱讀:

with open() as infile:
    for line in infile:

或者B.以塊的形式將文件讀入內存並進行處理,一次說250 MB?

處理不是很復雜,我只是將column1中的值抓到List1 ,將column2 List2等。可能需要一起添加一些列值。

我在具有30GB內存的Linux機器上使用python 2.7。 ASCII文本。

有什么方法可以加速並行? 現在我正在使用前一種方法,而且過程非常緩慢。 使用任何CSVReader模塊都可以提供幫助嗎? 我不必在python中使用它,任何其他語言或數據庫使用的想法都是受歡迎的。

聽起來你的代碼是I / O綁定的。 這意味着多處理不會有所幫助 - 如果您花費90%的時間從磁盤讀取數據,那么在下次讀取時等待額外的7個進程對任何事情都無濟於事。

而且,雖然使用CSV讀取模塊(無論是stdlib的csv還是像NumPy或Pandas這樣的東西)可能是一個簡單的好主意,但它不太可能在性能上有很大的不同。

不過,值得檢查一下你是否真的 I / O限制,而不僅僅是猜測。 運行程序,查看CPU使用率是接近0%還是接近100%或核心。 做Amadan在評論中提出的建議,並運行你的程序,只需pass處理,看看是否會減少5%的時間或70%。 您甚至可能想嘗試與os.openos.read(1024*1024)的循環進行比較,看看是否更快。


由於你使用Python 2.x,Python依靠C stdio庫來猜測一次緩沖多少,因此可能值得強制緩沖更多。 最簡單的方法是對某些大型bufsize使用readlines(bufsize) (您可以嘗試不同的數字並測量它們以查看峰值的位置。根據我的經驗,通常64K-8MB的任何東西大致相同,但取決於您的系統可能會有所不同 - 特別是如果您是,例如,閱讀關閉網絡文件系統,吞吐量很大,但延遲時間很長,淹沒了實際物理驅動器的吞吐量與延遲以及操作系統的緩存。)

所以,例如:

bufsize = 65536
with open(path) as infile: 
    while True:
        lines = infile.readlines(bufsize)
        if not lines:
            break
        for line in lines:
            process(line)

同時,假設您使用的是64位系統,您可能希望嘗試使用mmap而不是首先讀取文件。 這當然不能保證更好,但可能會更好,具體取決於您的系統。 例如:

with open(path) as infile:
    m = mmap.mmap(infile, 0, access=mmap.ACCESS_READ)

Python mmap是一種奇怪的對象 - 它同時像一個str ,就像一個file一樣,所以你可以,例如,手動迭代掃描換行,或者你可以像readline文件一樣調用readline 這兩個將從Python中進行更多的處理,而不是將文件作為行或批處理readlines線進行迭代(因為在C中的循環現在是純Python ...雖然也許你可以用re或者簡單的Cython擴展來解決這個問題?)...但操作系統的I / O優勢知道你正在使用映射做什么可能會淹沒CPU的劣勢。

不幸的是,Python沒有暴露你用來調整東西的madvise調用,試圖在C中優化它(例如,顯式設置MADV_SEQUENTIAL而不是使內核猜測,或強制透明的大頁面) - 但你實際上可以ctypes libc的功能。

我知道這個問題很古老; 但我想做類似的事情,我創建了一個簡單的框架,可以幫助您並行讀取和處理大文件。 留下我嘗試的答案。

這是代碼,我最后給出了一個例子

def chunkify_file(fname, size=1024*1024*1000, skiplines=-1):
    """
    function to divide a large text file into chunks each having size ~= size so that the chunks are line aligned

    Params : 
        fname : path to the file to be chunked
        size : size of each chink is ~> this
        skiplines : number of lines in the begining to skip, -1 means don't skip any lines
    Returns : 
        start and end position of chunks in Bytes
    """
    chunks = []
    fileEnd = os.path.getsize(fname)
    with open(fname, "rb") as f:
        if(skiplines > 0):
            for i in range(skiplines):
                f.readline()

        chunkEnd = f.tell()
        count = 0
        while True:
            chunkStart = chunkEnd
            f.seek(f.tell() + size, os.SEEK_SET)
            f.readline()  # make this chunk line aligned
            chunkEnd = f.tell()
            chunks.append((chunkStart, chunkEnd - chunkStart, fname))
            count+=1

            if chunkEnd > fileEnd:
                break
    return chunks

def parallel_apply_line_by_line_chunk(chunk_data):
    """
    function to apply a function to each line in a chunk

    Params :
        chunk_data : the data for this chunk 
    Returns :
        list of the non-None results for this chunk
    """
    chunk_start, chunk_size, file_path, func_apply = chunk_data[:4]
    func_args = chunk_data[4:]

    t1 = time.time()
    chunk_res = []
    with open(file_path, "rb") as f:
        f.seek(chunk_start)
        cont = f.read(chunk_size).decode(encoding='utf-8')
        lines = cont.splitlines()

        for i,line in enumerate(lines):
            ret = func_apply(line, *func_args)
            if(ret != None):
                chunk_res.append(ret)
    return chunk_res

def parallel_apply_line_by_line(input_file_path, chunk_size_factor, num_procs, skiplines, func_apply, func_args, fout=None):
    """
    function to apply a supplied function line by line in parallel

    Params :
        input_file_path : path to input file
        chunk_size_factor : size of 1 chunk in MB
        num_procs : number of parallel processes to spawn, max used is num of available cores - 1
        skiplines : number of top lines to skip while processing
        func_apply : a function which expects a line and outputs None for lines we don't want processed
        func_args : arguments to function func_apply
        fout : do we want to output the processed lines to a file
    Returns :
        list of the non-None results obtained be processing each line
    """
    num_parallel = min(num_procs, psutil.cpu_count()) - 1

    jobs = chunkify_file(input_file_path, 1024 * 1024 * chunk_size_factor, skiplines)

    jobs = [list(x) + [func_apply] + func_args for x in jobs]

    print("Starting the parallel pool for {} jobs ".format(len(jobs)))

    lines_counter = 0

    pool = mp.Pool(num_parallel, maxtasksperchild=1000)  # maxtaskperchild - if not supplied some weird happend and memory blows as the processes keep on lingering

    outputs = []
    for i in range(0, len(jobs), num_parallel):
        print("Chunk start = ", i)
        t1 = time.time()
        chunk_outputs = pool.map(parallel_apply_line_by_line_chunk, jobs[i : i + num_parallel])

        for i, subl in enumerate(chunk_outputs):
            for x in subl:
                if(fout != None):
                    print(x, file=fout)
                else:
                    outputs.append(x)
                lines_counter += 1
        del(chunk_outputs)
        gc.collect()
        print("All Done in time ", time.time() - t1)

    print("Total lines we have = {}".format(lines_counter))

    pool.close()
    pool.terminate()
    return outputs

比方說,我有一個文件,我想在其中計算每行中的單詞數,然后每行的處理看起來像

def count_words_line(line):
    return len(line.strip().split())

然后調用函數如:

parallel_apply_line_by_line(input_file_path, 100, 8, 0, count_words_line, [], fout=None)

使用這個,我獲得了大約8倍的速度,相比於大小為〜20GB的樣本文件中的vanilla逐行讀取,其中我在每行上進行了一些中等復雜的處理。

暫無
暫無

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

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