Есть ли более быстрый способ (чем этот) вычислить хэш файла (используя hashlib) в Python?

Мой текущий подход таков:

def get_hash(path=PATH, hash_type='md5'):
    func = getattr(hashlib, hash_type)()
    with open(path, 'rb') as f:
         for block in iter(lambda: f.read(1024*func.block_size, b''):
             func.update(block)
    return func.hexdigest()

Для вычисления md5sum iso-файла размером 842 МБ на i5 @ 1,7 ГГц требуется около 3,5 секунд. Я пробовал разные способы чтения файла, но все они дают более медленный результат. Возможно, есть более быстрое решение?

РЕДАКТИРОВАТЬ: я заменил 2**16 (внутри f.read()) на 1024*func.block_size, так как block_size по умолчанию для большинства функций хеширования, поддерживаемых hashlib, равно 64 (за исключением 'sha384' и 'sha512' - для них block_size по умолчанию равно 128). Поэтому размер блока остался прежним (65536 бит).

РЕДАКТИРОВАТЬ (2): я сделал что-то не так. Это занимает 8,4 секунды вместо 3,5. :(

РЕДАКТИРОВАТЬ (3): по-видимому, Windows использовала диск на + 80%, когда я снова запустил функцию. Это действительно занимает 3,5 секунды. Фу.

Другое решение (~-0,5 секунды, немного быстрее) — использовать os.open():

def get_hash(path=PATH, hash_type='md5'):
    func = getattr(hashlib, hash_type)()
    f = os.open(path, (os.O_RDWR | os.O_BINARY))
    for block in iter(lambda: os.read(f, 2048*func.block_size), b''):
        func.update(block)
    os.close(f)
    return func.hexdigest()

Обратите внимание, что эти результаты не являются окончательными.


person Deneb    schedule 29.03.2014    source источник
comment
Насколько быстро можно вычислить MD5 этого файла с помощью инструмента md5sum?   -  person    schedule 29.03.2014
comment
@LutzHorn Поскольку в настоящее время я не использую дистрибутив Linux / Gnu, используя 32-битную md5sum Gnu для Windows, это занимает 8,5257151 секунды.   -  person Deneb    schedule 29.03.2014
comment
Так что Python не так уж и плох :)   -  person    schedule 29.03.2014
comment
Попробуйте использовать os.open(), если вы еще не используете его.   -  person martineau    schedule 29.03.2014
comment
@martineau Можете ли вы уточнить?   -  person Deneb    schedule 29.03.2014
comment
Вам придется немного покопаться, чтобы найти его в модуле ОС документации, но это более низкоуровневая версия встроенной функции open(), которая возвращает файловый объект, что звучит как своего рода оболочка, поэтому использование первого может повлечь за собой меньшие накладные расходы.   -  person martineau    schedule 29.03.2014
comment
P.S. Вам также потребуется использовать os.read().   -  person martineau    schedule 29.03.2014
comment
@martineau Проверьте мой ответ.   -  person Deneb    schedule 29.03.2014
comment
Попробуйте block_size * 32768, что составляет 2 МБ.   -  person greggo    schedule 30.03.2014
comment
@greggo Это мало что меняет.   -  person Deneb    schedule 30.03.2014
comment
Нет смысла использовать hashfunc.block_size вообще, это бессмысленное значение, которое существует только как часть API по устаревшим причинам. Просто зациклите чтение любого размера, который эффективен для чтения с диска для целей вашего кода, и передайте его хеш-функции. Пока вы читаете более ~ 64 КБ за раз, вы вряд ли заметите какую-либо измеримую разницу.   -  person gps    schedule 30.03.2014
comment
@gps На самом деле есть заметные различия. Существенно увеличьте или уменьшите block_size, и ваша функция будет работать на несколько секунд быстрее или медленнее.   -  person Deneb    schedule 30.03.2014
comment
Я хотел сказать, что атрибут block_size хеш-функций совершенно бесполезен. Вы не должны писать код, который его использует. Его изменение ничего не даст. Единственное, что имеет значение, — это изменение размера буфера ввода/вывода. Это не имеет ничего общего с размером внутреннего блока хеш-функции.   -  person gps    schedule 04.04.2014
comment
@gps, я знаю это. Дело в том, что я не изменяю атрибут block_size хеш-функции. Я изменяю размер буфера ввода-вывода, используя в качестве параметра значение block_size по умолчанию (умноженное на какое-то другое значение).   -  person Deneb    schedule 04.04.2014
comment
Я хочу сказать, что атрибут block_size хэш-функции не имеет смысла и даже не должен использоваться ни для каких целей. Просто выберите размер буфера ввода-вывода, не пытайтесь основывать его на block_size.   -  person gps    schedule 17.04.2014
comment
@Deneb На какое время обработки вы рассчитываете? Кажется, что текущее время обработки близко к тому, что технически возможно. Оптимизация без четких измерений (что вы делаете) и целевого результата (которого я здесь упускаю) вскоре может превратиться в бесконечную трату времени.   -  person Jan Vlcinsky    schedule 18.04.2014


Ответы (1)


Используя файл случайных данных размером 874 МБ, который потребовал 2 секунды с инструментом md5 openssl, я смог улучшить скорость следующим образом.

  • Использование вашего первого метода потребовало 21 секунды.
  • Чтение всего файла (21 секунда) в буфер и последующее обновление требуется 2 секунды.
  • Использование следующей функции с размером буфера 8096 потребовало 17 секунд.
  • Использование следующей функции с размером буфера 32767 потребовало 11 секунд.
  • Использование следующей функции с размером буфера 65536 потребовало 8 секунд.
  • Использование следующей функции с размером буфера 131072 потребовало 8 секунд.
  • Использование следующей функции с размером буфера 1048576 потребовало 12 секунд.
def md5_speedcheck(path, size):
    pts = time.process_time()
    ats = time.time()
    m = hashlib.md5()
    with open(path, 'rb') as f:
        b = f.read(size)
        while len(b) > 0:
            m.update(b)
            b = f.read(size)
    print("{0:.3f} s".format(time.process_time() - pts))
    print("{0:.3f} s".format(time.time() - ats))

Человеческое время — это то, что я отметил выше. Принимая во внимание, что время процессора для всех них примерно одинаково с разницей в блокировке ввода-вывода.

Ключевым определяющим фактором здесь является размер буфера, который достаточно велик, чтобы уменьшить задержку диска, но достаточно мал, чтобы избежать подкачки страниц VM. Для моей конкретной машины кажется, что 64 КБ оптимальны.

person Lance Helsten    schedule 09.05.2014
comment
Есть ли способ, как размер буфера может быть выбран автоматически? - person Tom de Geus; 01.12.2020