簡體   English   中英

讀取具有定義格式的二進制文件的最快方法?

[英]Fastest way to read a binary file with a defined format?

我有具有預定義格式的大型二進制數據文件,最初由 Fortran 程序編寫為 little endians。 我想以最快、最有效的方式讀取這些文件,因此按照提高讀取二進制文件和轉換二進制文件的速度中的建議,使用array包似乎正合我意。 .

問題是預定義格式是非同質的。 它看起來像這樣: ['<2i','<5d','<2i','<d','<i','<3d','<2i','<3d','<i','<d','<i','<3d']

每個整數i占用 4 個字節,每個雙精度d占用 8 個字節。

有沒有辦法我仍然可以使用超級高效的array包(或其他建議)但格式正確?

使用struct 特別是struct.unpack

result = struct.unpack("<2i5d...", buffer)

這里buffer保存給定的二進制數據。

從您的問題中不清楚您是否關心實際的文件讀取速度(以及在內存中構建數據結構),或者關心以后的數據處理速度。

如果您只讀取一次,然后進行大量處理,則可以逐條讀取文件記錄(如果您的二進制數據是具有相同格式的重復記錄的記錄集),使用struct.unpack解析它並將其附加到[double]數組:

from functools import partial

data = array.array('d')
record_size_in_bytes = 9*4 + 16*8   # 9 ints + 16 doubles

with open('input', 'rb') as fin:
    for record in iter(partial(fin.read, record_size_in_bytes), b''):
        values = struct.unpack("<2i5d...", record)
        data.extend(values)

假設您被允許將所有int s 轉換為double s願意接受分配內存大小的增加(問題記錄增加 22%)。

如果您多次從文件中讀取數據,那么將所有內容轉換為一個大型double array (如上)並將其寫回到另一個文件中是值得的,您稍后可以使用array.fromfile()讀取數據:

data = array.array('d')
with open('preprocessed', 'rb') as fin:
    n = os.fstat(fin.fileno()).st_size // 8
    data.fromfile(fin, n)

更新 多虧了@martineau 的一個很好的基准測試,現在我們知道了一個事實,即預處理數據並將其轉換為同質雙精度數組可確保從文件(使用array.fromfile() )加載此類數據的速度快~20x to ~40x倍而不是逐條記錄地讀取它,解包並附加到array (如上面的第一個代碼清單所示)。

@martineau 的答案中逐條記錄讀取的更快(和更標准)變化附加到list並且不向上轉換為double僅比array.fromfile()方法慢~6x to ~10x並且看起來像一個更好的參考基准。

主要更新:修改為使用正確的代碼讀取預處理數組文件(下面的函數using_preprocessed_file() ),這顯着改變了結果。

為了確定 Python 中哪種方法更快(僅使用內置函數和標准庫),我創建了一個腳本來對可用於執行此操作的不同技術進行基准測試(通過timeit )。 它有點偏長,所以為了避免分心,我只發布經過測試的代碼和相關結果。 (如果對該方法有足夠的興趣,我將發布整個腳本。)

以下是比較的代碼片段:

@TESTCASE('Read and constuct piecemeal with struct')
def read_file_piecemeal():
    structures = []
    with open(test_filenames[0], 'rb') as inp:
        size = fmt1.size
        while True:
            buffer = inp.read(size)
            if len(buffer) != size:  # EOF?
                break
            structures.append(fmt1.unpack(buffer))
    return structures

@TESTCASE('Read all-at-once, then slice and struct')
def read_entire_file():
    offset, unpack, size = 0, fmt1.unpack, fmt1.size
    structures = []
    with open(test_filenames[0], 'rb') as inp:
        buffer = inp.read()  # read entire file
        while True:
            chunk = buffer[offset: offset+size]
            if len(chunk) != size:  # EOF?
                break
            structures.append(unpack(chunk))
            offset += size

    return structures

@TESTCASE('Convert to array (@randomir part 1)')
def convert_to_array():
    data = array.array('d')
    record_size_in_bytes = 9*4 + 16*8   # 9 ints + 16 doubles (standard sizes)

    with open(test_filenames[0], 'rb') as fin:
        for record in iter(partial(fin.read, record_size_in_bytes), b''):
            values = struct.unpack("<2i5d2idi3d2i3didi3d", record)
            data.extend(values)

    return data

@TESTCASE('Read array file (@randomir part 2)', setup='create_preprocessed_file')
def using_preprocessed_file():
    data = array.array('d')
    with open(test_filenames[1], 'rb') as fin:
        n = os.fstat(fin.fileno()).st_size // 8
        data.fromfile(fin, n)
    return data

def create_preprocessed_file():
    """ Save array created by convert_to_array() into a separate test file. """
    test_filename = test_filenames[1]
    if not os.path.isfile(test_filename):  # doesn't already exist?
        data = convert_to_array()
        with open(test_filename, 'wb') as file:
            data.tofile(file)

這是在我的系統上運行它們的結果:

Fastest to slowest execution speeds using Python 3.6.1
(10 executions, best of 3 repetitions)
Size of structure: 164
Number of structures in test file: 40,000
file size: 6,560,000 bytes

     Read array file (@randomir part 2): 0.06430 secs, relative  1.00x (   0.00% slower)
Read all-at-once, then slice and struct: 0.39634 secs, relative  6.16x ( 516.36% slower)
Read and constuct piecemeal with struct: 0.43283 secs, relative  6.73x ( 573.09% slower)
    Convert to array (@randomir part 1): 1.38310 secs, relative 21.51x (2050.87% slower)

有趣的是,大多數代碼片段在 Python 2 中實際上更快......

Fastest to slowest execution speeds using Python 2.7.13
(10 executions, best of 3 repetitions)
Size of structure: 164
Number of structures in test file: 40,000
file size: 6,560,000 bytes

     Read array file (@randomir part 2): 0.03586 secs, relative  1.00x (   0.00% slower)
Read all-at-once, then slice and struct: 0.27871 secs, relative  7.77x ( 677.17% slower)
Read and constuct piecemeal with struct: 0.40804 secs, relative 11.38x (1037.81% slower)
    Convert to array (@randomir part 1): 1.45830 secs, relative 40.66x (3966.41% slower)

查看numpyfromfile函數的文檔: https ://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.fromfile.html 和https://docs.scipy.org /doc/numpy/reference/arrays.dtypes.html#arrays-dtypes-constructing

最簡單的例子:

import numpy as np
data = np.fromfile('binary_file', dtype=np.dtype('<i8, ...'))

在此處詳細了解numpy中的“結構化數組”以及如何指定其數據類型: https ://docs.scipy.org/doc/numpy/user/basics.rec.html#

這里有很多好的和有用的答案,但我認為最好的解決方案需要更多解釋。 我實現了一種方法,使用內置的read()一次讀取整個數據文件,並同時構造一個numpy ndarray 這比單獨讀取數據和構造數組更有效,但也更挑剔一點。

line_cols = 20              #For example
line_rows = 40000           #For example
data_fmt = 15*'f8,'+5*'f4,' #For example (15 8-byte doubles + 5 4-byte floats)
data_bsize = 15*8 + 4*5     #For example
with open(filename,'rb') as f:
        data = np.ndarray(shape=(1,line_rows),
                          dtype=np.dtype(data_fmt),
                          buffer=f.read(line_rows*data_bsize))[0].astype(line_cols*'f8,').view(dtype='f8').reshape(line_rows,line_cols)[:,:-1]

在這里,我們使用 open 中的'rb'選項將文件作為二進制文件open 然后,我們構建具有適當形狀和數據類型的ndarray以適合我們的讀取緩沖區。 然后我們通過獲取第零個索引將ndarray為一維數組,我們所有的數據都隱藏在該索引中。 然后,我們使用np.astypenp.viewnp.reshape方法重塑數組。 這是因為np.reshape不喜歡混合數據類型的數據,而且我可以將整數表示為雙精度數。

這種方法比逐行循環數據快約 100 倍,並且有可能被壓縮成一行代碼。

將來,我可能會嘗試使用將二進制文件轉換為文本文件的Fortran腳本更快地讀取數據。 我不知道這是否會更快,但值得一試。

暫無
暫無

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

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