Чтение всего двоичного файла в Python - PullRequest
8 голосов
/ 12 декабря 2010

Мне нужно импортировать двоичный файл из Python - содержимое имеет 16-разрядные целые числа со знаком, с прямым порядком байтов.

Следующие вопросы о переполнении стека подсказывают, как загружать несколько байтов за раз, но разве это способ увеличить масштаб для чтения целого файла?

Я думал создать такую ​​функцию, как:

from numpy import *
import os

def readmyfile(filename, bytes=2, endian='>h'):
    totalBytes = os.path.getsize(filename)
    values = empty(totalBytes/bytes)
    with open(filename, 'rb') as f:
        for i in range(len(values)):
            values[i] = struct.unpack(endian, f.read(bytes))[0]
    return values

filecontents = readmyfile('filename')

Но это довольно медленно (файл 165924350 байт). Есть ли лучший способ?

Ответы [ 5 ]

10 голосов
/ 12 декабря 2010

Использование numpy.fromfile.

4 голосов
/ 12 декабря 2010

Я бы непосредственно читал до EOF (это означает проверку на получение пустой строки), исключая тогда необходимость использования range () и getsize.
Кроме того, использование xrange (вместо range) должно улучшить ситуацию, особенно в отношении использования памяти.
Более того, как предположил Фальмарри, одновременное чтение большего количества данных значительно улучшило бы производительность.

Тем не менее, я не ожидал бы чудес, в том числе потому, что я не уверен, что список является наиболее эффективным способом хранения всего этого количества данных.
Как насчет использования массива NumPy и его возможностей для чтения / записи двоичных файлов ? В этой ссылке есть раздел о чтении необработанных двоичных файлов с использованием numpyio.fread. Я считаю, что это должно быть именно то, что вам нужно.

Примечание: лично я никогда не использовал NumPy; тем не менее, его основной смысл - именно обработка больших наборов данных - и это то, что вы делаете в своем вопросе.

2 голосов
/ 06 июля 2012

У меня была такая же проблема, хотя в моем конкретном случае мне пришлось конвертировать очень странный файл двоичного формата (500 МБ) с чересстрочными блоками из 166 элементов, которые представляли собой 3-байтовые целые числа со знаком;поэтому у меня возникла проблема с преобразованием из 24-разрядных в 32-разрядные целые числа со знаком, что немного замедляет процесс.

Я решил эту проблему с помощью карты памяти NumPy (это просто удобный способ использования Pythonmemmap) и struct.unpack на большой части файла.

С помощью этого решения я могу конвертировать (читать, делать вещи и записывать на диск) весь файл за 90 секунд (по времени.clock ()).

Я могу загрузить часть кода.

2 голосов
/ 12 декабря 2010

Вы читаете и распаковываете 2 байта за раз

values[i] = struct.unpack(endian,f.read(bytes))[0]

Почему вы не читаете как, 1024 байта за раз?

1 голос
/ 13 декабря 2010

Я думаю, что узкое место у вас здесь двоякое.

В зависимости от вашей ОС и контроллера диска, вызовы f.read(2), где f является большим файлом, обычно эффективно буферизируются - обычно .Другими словами, ОС будет считывать один или два сектора (с секторами диска, обычно несколько килобайт) с диска в память, потому что это не намного дороже, чем чтение 2 байтов из этого файла.Дополнительные байты эффективно кэшируются в памяти и готовы к следующему вызову для чтения этого файла.Не полагайтесь на это поведение - это может быть вашим узким местом - но я думаю, что здесь есть другие проблемы.

Меня больше беспокоит однобайтовое преобразование в короткий и одиночный вызов numpy.Они не кешируются вообще.Вы можете сохранить все шорты в списке целых чисел Python и преобразовать весь список в numy, когда (и если) необходимо.Вы также можете сделать один вызов struct.unpack_from, чтобы преобразовать все в буфере против одного короткого за раз.

Обратите внимание:

#!/usr/bin/python

import random
import os
import struct
import numpy
import ctypes

def read_wopper(filename,bytes=2,endian='>h'):
    buf_size=1024*2
    buf=ctypes.create_string_buffer(buf_size)
    new_buf=[]

    with open(filename,'rb') as f:
        while True:
            st=f.read(buf_size)
            l=len(st)
            if l==0: 
                break
            fmt=endian[0]+str(l/bytes)+endian[1]    
            new_buf+=(struct.unpack_from(fmt,st))

    na=numpy.array(new_buf)        
    return na

fn='bigintfile'

def createmyfile(filename):
    bytes=165924350
    endian='>h'
    f=open(filename,"wb")
    count=0

    try: 
        for int in range(0,bytes/2):
            # The first 32,767 values are [0,1,2..0x7FFF] 
            # to allow testing the read values with new_buf[value<0x7FFF]
            value=count if count<0x7FFF else random.randint(-32767,32767)
            count+=1
            f.write(struct.pack(endian,value&0x7FFF))

    except IOError:
        print "file error"

    finally:
        f.close()

if not os.path.exists(fn):
    print "creating file, don't count this..."
    createmyfile(fn)
else:    
    read_wopper(fn)
    print "Done!"

Я создал файл случайных шорт со знаком с целым числом 165 924 350 байтов (158,24 МБ), который соответствует 82 962 175 со знаком 2-байтовых шорт.С этим файлом я запустил функцию read_wopper, описанную выше, и она запустилась:

real        0m15.846s
user        0m12.416s
sys         0m3.426s

Если вам не нужно , чтобы шорты были пустыми, эта функция запускается через 6 секунд,Все это на OS X, Python 2.6.1, 64-битная, 2.93 ГГц Core i7, 8 ГБ оперативной памяти.Если вы измените buf_size=1024*2 в read_wopper на buf_size=2**16, время выполнения будет:

real        0m10.810s
user        0m10.156s
sys         0m0.651s

Таким образом, я думаю, что ваша основная горлышко бутылки - это однобайтовые вызовы для распаковки, а не 2-байтовыечитает с диска.Возможно, вы захотите убедиться, что ваши файлы данных не фрагментированы, и если вы используете OS X, что ваше свободное дисковое пространство здесь ) не фрагментировано.

Редактировать Я разместил полный код, чтобы создать, а затем прочитать двоичный файл целых.На моем iMac я последовательно получаю <15 секунд, чтобы прочитать файл случайных целых.Создание занимает около 1:23, поскольку создание по одному короткое за раз.</p>

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...