Pythonのシリアライズ/デシリアライズ

こんにちは。gumiの木村です。
すっかり秋めいて寒い位になりましたが皆さんいかがお過ごしでしょうか。

さて、今回はPythonシリアライズを題材に書いてみたいと思います。
シリアライズとは、簡単にいうと数値・リスト等のオブジェクトをファイルやDB等に保存できる形式に変換することです。シリアライズされたものに対し逆変換を行いデータに戻すことをデシリアライズといいます。

Pythonシリアライズ/デシリアライズを行なう方法を以下にいくつか挙げてみました。

str, eval 関数

 シリアライズにデータ値を文字列に変換するstr関数、
 デシリアライズに文字列を評価するeval関数を使用します。

pickle, cPickle モジュール

 Pythonに標準で付属するpickleモジュールを使用する方法。シリアライズされたデータは独自形式になります。
 更に内部処理をCで記述し高速化されているcPickleが用意されています。

simplejson, json モジュール

 オブジェクトをjavascriptのデータ表現形式であるjsonに変換する方法。
 Python 2.6未満ではサードパーティ製のsimplejsonモジュール、2.6以降では標準付属のjsonモジュールが使用できます。

この中で一番使用が簡単なのはpickleです。ほとんどのオブジェクトを何も考えずにシリアライズできます。
ただし保存形式が独自なのでシリアライズされたデータを外部から加工する用途等には向きません。
シリアライズされたデータ形式の汎用性が高いのはpickle以外の方法ですが、速度的にどのシリアライズ/デシリアライズ方法が早いのか気になりますね。
そこで、それぞれの方法で整数データ、要素が整数10個のリスト、要素が整数10000個のリストを対象に計測を行ってみました。

ベンチマークプログラム ソース

import pickle
import cPickle
try:
    import json
except ImportError:
    import simplejson as json

import time


def bench(title, data, counts, func):
    start = time.time()
    for i in xrange(counts):
        outdata = func(data)
    end = time.time()
    e = int((end - start) * 1000)
    print '%-10s %-5s count:%5d %8dmsec' % (title, func.__name__, counts, e)
    return outdata

def benchmain(title, data, counts, funcs):
    print '========== %s' % title
    for title, seriarizer, deseriarizer in funcs:
        outdata = bench(title, data, counts, seriarizer)
        bench(title, outdata, counts, deseriarizer)

FUNCTIONS = (
    ('',  str, eval),
    ('pickle',  pickle.dumps, pickle.loads),
    ('cPickle', cPickle.dumps, cPickle.loads),
    ('json', json.dumps, json.loads),
)

if __name__ == '__main__':
    data = 1
    benchmain('Integer', data, 10000, FUNCTIONS)

    data = range(10)
    benchmain('Integer x10 list', data, 10000, FUNCTIONS)

    data = range(10000)
    benchmain('Integer x10000 list', data, 100, FUNCTIONS)

ベンチマークプログラム 実行結果

MacBook (Intel Core2Duo 2.4GHz, 4GBメモリ) 上で実行。

$ python -V
Python 2.5.4
$ python ./sbench.py
========== Integer
           str   count:10000        5msec
           eval  count:10000      134msec
pickle     dumps count:10000      103msec
pickle     loads count:10000      153msec
cPickle    dumps count:10000        9msec
cPickle    loads count:10000        9msec
json       dumps count:10000      112msec
json       loads count:10000       62msec
========== Integer x10 list
           str   count:10000       54msec
           eval  count:10000      415msec
pickle     dumps count:10000      619msec
pickle     loads count:10000      585msec
cPickle    dumps count:10000       49msec
cPickle    loads count:10000       74msec
json       dumps count:10000      179msec
json       loads count:10000       85msec
========== Integer x10000 list
           str   count:  100      357msec
           eval  count:  100     3381msec
pickle     dumps count:  100     4566msec
pickle     loads count:  100     3847msec
cPickle    dumps count:  100      351msec
cPickle    loads count:  100      504msec
json       dumps count:  100      476msec
json       loads count:  100      180msec

$ python -V
Python 2.6.1
$ python ./sbench.py
========== Integer
           str   count:10000        2msec
           eval  count:10000      126msec
pickle     dumps count:10000       76msec
pickle     loads count:10000      144msec
cPickle    dumps count:10000        8msec
cPickle    loads count:10000        6msec
json       dumps count:10000       64msec
json       loads count:10000      189msec
========== Integer x10 list
           str   count:10000       25msec
           eval  count:10000      354msec
pickle     dumps count:10000      411msec
pickle     loads count:10000      496msec
cPickle    dumps count:10000       38msec
cPickle    loads count:10000       53msec
json       dumps count:10000      354msec
json       loads count:10000     1728msec
========== Integer x10000 list
           str   count:  100      178msec
           eval  count:  100     3530msec
pickle     dumps count:  100     2957msec
pickle     loads count:  100     3020msec
cPickle    dumps count:  100      280msec
cPickle    loads count:  100      368msec
json       dumps count:  100     2345msec
json       loads count:  100    15805msec

興味深い結果が出ましたね。

  • strは早いがevalはかなり遅い
  • pickleは全般的に遅い
  • cPickleとsimplejsonを比較すると対象オブジェクトが小さい場合はcPickle, 大きい場合はsimplejsonが早い
  • cPickleとjsonを比較すると全般的にcPickleの方が早い

さらに、他のオブジェクトを対象にした場合や、他の方法によるシリアライズ/デシリアライズの場合を
計ってみると面白いかもしれません。