rcmdnk's blog

Checksum: Websters Timeline History, 1984 - 2004

Pythonで大きなファイルを扱う時に、 一気に読み込もうとしてメモリが足りずに MemoryErrorを起こしてしまった時の対処法。

Pythonでの大きなファイルのMD5チェックサム取得方法

ファイルサイズとかキにせずにチェックサム(ハッシュ値)を取ろうとすれば、

1
2
3
import hashlib
with open('test_file', 'rb') as f:
    checksum = hashlib.md5(f.read()).hexdigest()

hashlibmd5形式(sha1等も使える)のハッシュを作成して hexdigestメソッドを使うとこれを16進法形式で返してくれます。

ファイルを読むこむ時にはbを指定してバイナリ形式で読み込みます。

ただ、これだとf.read()で全部一気に読み込むので、 ファイルサイズが大きいとメモリが足りなくてMemoryErrorを出すことがあります。

この様な場合に備えて、ファイルを一気に読み込むのではなく、 必要分ずつ読み込んでハッシュ値を形成する様にすれば いくらでも大きなファイルのハッシュ値も扱える様になります。

1
2
3
4
5
6
import hashlib
md5 = hashlib.md5()
with open('test_file', 'rb') as f:
    for chunk in iter(lambda: f.read(2048 * md5.block_size), b''):
        md5.update(chunk)
checksum = md5.hexdigest()

こんな感じで2048 * hashlib.md5().block_size毎に読み出して updateで値を加えてチェックサムを作っていくことが出来ます。

iterは第一引数で呼び出した物が第二引数と一致しない限り 第一引数を返すメソッドですが、 第二引数に単にもう何も無いよ、と''を入れるのではなく、 b''としてるのはこの文字をbytes literalとして認識させるため。

ですが、これはPython3で必要ですがPython2ではbを付けなくても同じです。 (bを付けても無視される。)1

block_sizeはハッシュを計算する時に使う一つ際のブロックサイズなので、 読み込む時にはこの整数倍毎に読み込んでやれば、 全体を一気に読み込む時と同じ値を作ることが出来ます。

Macや手元のLinuxではこの値は64byteになっていました。

読み込む時に余り小さい値にしてしまうと、 細かく分けて読み込むのに時間がかかってしまうので適度に大きな値にします。 (ここでは2048*64 = 131072 byte = 128 kB.)

ちょっと10GBのファイルでテストしてみます。

mdtest.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env python

import hashlib
import time

filename = 'test_file'

t_start = time.clock()
with open(filename, 'rb') as f:
    print hashlib.md5(f.read()).hexdigest()
t_end = time.clock()
print "%10s: %f" % ("All", (t_end - t_start))

def get_md5(size):
    t_start = time.clock()
    md5 = hashlib.md5()
    with open(filename, 'rb') as f:
        for chunk in iter(lambda: f.read(size * md5.block_size), b''):
            md5.update(chunk)
    print md5.hexdigest()
    t_end = time.clock()
    print "%6d * %d: %f" % (size, md5.block_size, (t_end - t_start))

for s in [1, 16, 128, 256, 512, 1024, 2048, 4096, 8192, 16384]:
    get_md5(s)

こんな感じのテストスクリプトを書いて、ddで10GBのファイルを作ってテストしてみます。

$ dd if=/dev/zero of=test_file bs=1048576 count=10240
10240+0 records in
10240+0 records out
10737418240 bytes transferred in 16.104331 secs (666741029 bytes/sec)
$ du -h test_file
10.0G   test_file
$ time md5sum test_file
2dd26c4d4799ebd29fa31e48d49e8e53  test_file

real    0m24.139s
user    0m19.463s
sys     0m3.994s
(-_-) $ ./md5test.py
2dd26c4d4799ebd29fa31e48d49e8e53
       All: 37.094055
2dd26c4d4799ebd29fa31e48d49e8e53
    1 * 64: 223.921650
2dd26c4d4799ebd29fa31e48d49e8e53
   16 * 64: 36.051889
2dd26c4d4799ebd29fa31e48d49e8e53
  124 * 64: 23.426525
2dd26c4d4799ebd29fa31e48d49e8e53
  256 * 64: 21.920792
2dd26c4d4799ebd29fa31e48d49e8e53
  512 * 64: 21.080897
2dd26c4d4799ebd29fa31e48d49e8e53
 1024 * 64: 20.728919
2dd26c4d4799ebd29fa31e48d49e8e53
 2048 * 64: 20.639481
2dd26c4d4799ebd29fa31e48d49e8e53
 4096 * 64: 20.590142
2dd26c4d4799ebd29fa31e48d49e8e53
 8192 * 64: 20.617627
2dd26c4d4799ebd29fa31e48d49e8e53
16384 * 64: 20.696506

一応毎回チェックサムの結果を出してますが、全部同じになっています。

コマンドラインからmd5sumを使って計算すると 24秒程度ですが、 Pythonで全て一気に読み込むと37秒かかりました。 (16GBのメモリマシンですがMemoryErrorなしで読み込めました。)

また、最小単位の64byte毎に読み込むと224秒と大分時間がかかります。

徐々に短縮されていって4096*64=262144 byte = 256 kBの時が 一番速く20.6秒で終えています。

その後はサイズを大きくしていっても徐々に逆に遅くなる感じ。

勿論環境によりますが、数GB~10数GB程度のメモリだとして、 大体1kB ~ 1MB位のサイズで読みこめば 十分速く読み込め、かつMemoryErrorも出ない感じです。

一方、md5sumコマンドも大きなファイルもきちんと扱ってくれるし 十分速いので、 Pythonの中でも

import commands
print commands.getoutput('md5sum %s' % filename)

と呼んでしまうのも一つの手かもしれません。 (ただし、md5sumコマンドが無い環境は結構あるので注意。)

参考:

Get MD5 hash of big files in Python - Stack Overflow

MacでMD5チェックサム

MD5のついで。

ちょっとチェックしようと思ってMacで作業してたら Macにはmd5sumコマンドが入っていませんでした。

shaハッシュを調べるshasumコマンドは入っています。

Homebrewでチェックしてみるとmd5sha1sumというのがあったのでこれを入れてみると、 md5sumコマンドが入りました。 ついでにsha1sumというsha1専用のコマンドも入りました。 (sha256sumとかはHomebrewには無さそう?)

MD5チェックサムを調べるには

$ md5sum test_file

SHA1チェックサムなら

$ sha1sum test_file

または

$ shasum -a 1 test_file

SHA256なら

$ shasum -a 256 test_file
Sponsored Links
Sponsored Links

« URLを絵文字にしてくれるURL短縮サービス GNU/BSDでのsedにおける正規表現の扱いの違い »

}