Вычисление md5. Что не так с Питоном?

(Аббревиатура «MD5» переводится как Message Digest 5-ой версии)

Это третья статья на тему вычисления хэш-суммы. Первые две статьи здесь:

http://wp.me/p1H7g0-1aC
http://wp.me/p1H7g0-1aJ

Не спешите напрягаться и строить версии — с Питоном всё в порядке! Это с нашим представлением (с моим — в частности) о работе Питона не всё хорошо. Поэтому полученные результаты меня повергли в легкий шок.

Вычислять хэш-суммы статических объектов неинтересно. Намного полезнее вычислять статические суммы файлов.

На данный момент у нас есть три варианта вычисления хэша:

  1. С помощью самопальной программы на Питоне.
  2. С помощью самопальной программы на Си.
  3. С помощью стандартной Линуксовой утилиты md5sum.

Начнем с последней. Для тестирования нам понадобится достаточно большой файл. Я тупо взял какой-то видео-файл размером чуть более 800 МБ. Для удобства работы с ним я переименовал его в bigfile.

Для того чтобы вычислить хэш-сумму этого файла нужно в консоли выполнить команду:

$ md5sum bigfile

В результате будет выведена строка из 32-ух шестнадцатиричных цифр. Забегая вперед скажу, что при правильной работе все три варианта подсчета хэш-суммы должны выдавать одинаковое число. (Иначе что-то не так в тексте программ, типа где-то сидит ошибка!)

Получение одинаковых хэш-сумм говорит лишь о том, что метод подсчета работает для всех трех вариантов. Это очевидно! Но это не интересно. Гораздо интереснее сравнить скорость работы этих вариантов.

Поскольку мы уже «упражняемся» с утилитой, то давайте посмотрим сколько времени занимает ее работа. Для этого заключим ее (утилиту) в объятия команды time:

$ time md5sum bigfile 
76bdc697a461bf6907ba042ca3dff5dd  bigfile

real	0m12.074s
user	0m3.296s
sys	0m0.800s

Будет лучше, если вы запустите эту команду раза три-пять. По крайней мере вы получите примерный диапазон значений времени выполнения. У меня старенький комп, поэтому время работы утилиты у меня составляет примерно 12.0-12.4 сек. Если у вас прокачанный комп, то вам лучше взять файл большего объема, хотя это и не принципиально. Просто с временами, находящимися  в районе 5-10 секунд, работать комфортнее. С одной стороны — это недолго ждать, а с другой — точность измерения получается более-менее адекватной.

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

Ниже — готовый вариант такой программы. Его нужно откомпилировать, как это мы делали во второй статье.

/* md5-file.c */

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <openssl/md5.h>

#define BUFSIZE (4096) /* Размер буфера */

int main(int argc, char **argv)
{
  int i, n;
  MD5_CTX hMD5;
  uint8_t digest[MD5_DIGEST_LENGTH];
  FILE *pFin;
  uint8_t buf[BUFSIZE];
  
  pFin = fopen(argv[1], "rb");
  
  if (pFin == NULL)
  {
    fprintf(stderr, "Не могу открыть файл: %s\n", argv[1]);
    return EXIT_FAILURE;
  }
  
  MD5_Init(&hMD5);

  while (1)
  {
    n = fread(buf, 1, BUFSIZE, pFin);
    if (n == 0)
      break;

    MD5_Update(&hMD5, buf, n);
  }
  
  fclose(pFin);

  MD5_Final(digest, &hMD5);
			    
  for (i = 0; i &lt; MD5_DIGEST_LENGTH; i++)
    printf("%02x", digest[i]);
					    
  printf("\n");

  return EXIT_SUCCESS;
}

Я прошу критиков не обращать внимание на «неидеальность» кода. Конечно, в реальных коммерческих проектах память под буфер нужно выделять не статически на стеке, как это сделано здесь, а запрашивать у системы с помощью функций malloc/calloc. Ну есть и другие спорные моменты, типа можно оптимизировать цикл и проверку. Пожалуйста, не обращайте внимание на эти моменты! Не это главное!

Назначение этой программы не показать совершенный код по-Макконнеллу, а сделать код прозрачным для понимания. Это немного разные цели.

Однако, вернемся к делу! Откомпилировав исходник и запустив программу вы ожидаете получить значения времени выполнения, соизмеримые с значениями утилиты md5sum. Логично!

Поиграйтесь и убедитесь, что это так! По крайней мере, у меня на компе я не могу сказать однозначно — что работает быстрее: стандартная Линуксовая md5sum или наша программа md5-file.

А теперь переходим к самому интересному. Пристегните ремни безопасности!

Теперь мы должны переделать Питоноскую прогу для подсчета хэш-суммы файлов.

Вот, этот код:

#!/usr/bin/env python3
#coding:utf-8

import hashlib
import sys

md5 = hashlib.md5()

with open(sys.argv[1], 'rb') as f:
  while True:
    buf = f.read(4096)
    if len(buf) != 0:
      md5.update(buf)
    else:
      break
      
print(md5.hexdigest())

Подождите, пока не запускайте прогу!

Сначала ответьте себе на вопрос — на сколько медленнее Питон работает против Си?
Какое время выполнения вы ожидаете увидеть?

Назвали цифру? Отлично!

Мое представление о скорости работы Питона было где-то на уровне «в сто раз медленнее, чем Си». Ну, по крайней мере, Питон медленнее С-ей в разы. Согласны со мной? А теперь выпускайте на ринг нашу Питоновскую прогу!

Ну и как результат?

Питон — это не совсем тот язык программирования, каким мы его представляем. Питон — это язык-клей — для склеивания в единую программу быстрых кусков кода, которые написаны на Си/С++ и на ассемблере. Когда в программе «клея» совсем немного (то есть код программы написан правильно с точки зрения языка Питон, а не с точки зрения Си), то влияние медленного «клея» на скорость работы проги ничтожно мало.

Чтобы писать правильный Питоновский код, нужно думать на Питоне, а не на Си. Это сложно сделать, когда всю жизнь писал проги на Си/С++. Но если перестроить свое мышление, то результат будет очень поразительный.

Я даже больше скажу! Играя с размером буфера (1024, 4096, 16384, 65536 и 1048576 байт) в программах на Си и на Питоне, при значениях 16 и 64 кБ я даже получил устойчивый результат превосходства скорости выполнения Питоновской проги над скоростью работы Линуксовой утилиты md5sum. Скажу честно, меня это очень сильно удивило. Разница составляла около 15-20%.

Конечно, это не очень ощутимо в повседневной жизни, но это очень заметно при тестировании и это очень поразительно как факт. В общем, мои догмы относительно медленности работы Питона существенно поколебались.

One response to “Вычисление md5. Что не так с Питоном?

  1. Вы немного не туда смотрите,
    real 0m12.074s
    user 0m3.296s
    sys 0m0.800s
    Означает что ваш процесс реально занял 3.3+0.8 процессорных секунды, а остальное время он просто ожидал дисковый io.
    И в вашем примере непосредственно питон практически ничего не делает, лишь в цикле дергает 2 нативных метода read и md5.update, конечно затраты на это не велики. Но чем больше будет python кода — тем больше будет и разница🙂

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s