読者です 読者をやめる 読者になる 読者になる

PyYAMLが非常に遅かった

うすうす感じてはいたが測ってみたらPyYAMLがめちゃめちゃ遅い。しかもこの調査をするまでlibyamlを使えていなかったのでなおさら。
ということでテキストならujson、バイナリならmsgpackがおすすめ。どうしても可読性が必要な場合は容量を抑えるか速度の必要ないところでYAMLに変換する方が良さそうです。

import sys
import gzip
import timeit
import json
import yaml
import bson
import pymongo
import msgpack
import ujson

#=======================================================================================================================
def json_loads(dump):
    return json.loads(dump)

#=======================================================================================================================
def ujson_loads(dump):
    return ujson.loads(dump)

#=======================================================================================================================
def yaml_loads(dump):
    return yaml.load(dump)

#=======================================================================================================================
def cyaml_loads(dump):
    return yaml.load(dump, Loader=yaml.CLoader)

#=======================================================================================================================
def bson_loads(dump):
    return bson.BSON(dump).decode()

#=======================================================================================================================
def msgpack_loads(dump):
    return msgpack.loads(dump)

#=======================================================================================================================
def test_load():
    """load時間計測"""

    print '[load]'
    dump = gzip.open('data.json.gz', 'rb').read()
    print 'json.loads: %.5f sec' % (timeit.timeit(stmt=lambda : json_loads(dump), number=40) / 40)
    dump = gzip.open('data.yaml.gz', 'rb').read()
    print 'yaml.load: %.5f sec' % (timeit.timeit(stmt=lambda : yaml_loads(dump), number=1) / 1)
    if yaml.__with_libyaml__:
        dump = gzip.open('data.yaml.gz', 'rb').read()
        print 'yaml.load(libyaml): %.5f sec' % (timeit.timeit(stmt=lambda : cyaml_loads(dump), number=1) / 1)
    else:
        print 'yaml.load(libyaml):', '-'
    dump = gzip.open('data.bson.gz', 'rb').read()
    print 'bson.BSON.decode: %.5f sec' % (timeit.timeit(stmt=lambda : bson_loads(dump), number=110) / 110)
    dump = gzip.open('data.json.gz', 'rb').read()
    print 'ujson.loads: %.5f sec' % (timeit.timeit(stmt=lambda : ujson_loads(dump), number=100) / 100)
    dump = gzip.open('data.mpac.gz', 'rb').read()
    print 'msgpack.loads: %.5f sec' % (timeit.timeit(stmt=lambda : msgpack_loads(dump), number=250) / 250)
    print


#=======================================================================================================================
def json_dumps(data):
    return json.dumps(data)

#=======================================================================================================================
def ujson_dumps(data):
    return ujson.dumps(data)

#=======================================================================================================================
def yaml_dumps(data):
    return yaml.dump(data)

#=======================================================================================================================
def cyaml_dumps(data):
    return yaml.dump(data, Dumper=yaml.CDumper)

#=======================================================================================================================
def bson_dumps(data):
    return bson.BSON.encode(data)

#=======================================================================================================================
def msgpack_dumps(data):
    return msgpack.dumps(data)

#=======================================================================================================================
def test_dump():
    """dump時間計測"""

    dump = gzip.open('data.mpac.gz', 'rb').read()
    data = msgpack.loads(dump)

    print '[dump]'
    print 'json.dumps: %.5f sec' % (timeit.timeit(stmt=lambda : json_dumps(data), number=75) / 75)
    print 'yaml.dump: %.5f sec' % (timeit.timeit(stmt=lambda : yaml_dumps(data), number=1) / 1)
    if yaml.__with_libyaml__:
        print 'yaml.dump(libyaml): %.5f sec' % (timeit.timeit(stmt=lambda : cyaml_dumps(data), number=1) / 1)
    else:
        print 'yaml.dump(libyaml):', '-'
    print 'bson.BSON.encode: %.5f sec' % (timeit.timeit(stmt=lambda : bson_dumps(data), number=18) / 18)
    print 'ujson.dumps: %.5f sec' % (timeit.timeit(stmt=lambda : ujson_dumps(data), number=116) / 116)
    print 'msgpack.dumps: %.5f sec' % (timeit.timeit(stmt=lambda : msgpack.dumps(data), number=166) / 166)
    print


#=======================================================================================================================
def validationcheck():
    """dump/loadがちゃんと可逆になっているかの動作確認"""

    print '[data]'

    dump = gzip.open('data.json.gz', 'rb').read()
    dump = json_dumps(json_loads(dump))
    dump_len = len(dump)
    print 'data.json:', dump_len, 'bytes'
    dump = json_dumps(json_loads(dump))
    assert len(dump) == dump_len, len(dump)

    dump = ujson_dumps(ujson_loads(dump)) ### jsonとujsonとで出力の仕方が微妙に違うので再dumpしてからチェック
    dump_len = len(dump)
    print 'data.json:', dump_len, 'bytes', '(ujson)'
    dump = ujson_dumps(ujson_loads(dump))
    assert len(dump) == dump_len, len(dump)

    dump = gzip.open('data.yaml.gz', 'rb').read()
    dump = yaml_dumps(yaml_loads(dump))
    dump_len = len(dump)
    print 'data.yaml:', dump_len, 'bytes'
    dump = yaml_dumps(yaml_loads(dump))
    assert len(dump) == dump_len

    if yaml.__with_libyaml__:
        dump = cyaml_dumps(cyaml_loads(dump))
        dump_len = len(dump)
        print 'data.yaml:', dump_len, 'bytes', '(cyaml)'
        dump = cyaml_dumps(cyaml_loads(dump))
        assert len(dump) == dump_len

    dump = gzip.open('data.bson.gz', 'rb').read()
    dump_len = len(dump)
    print 'data.bson:', dump_len, 'bytes'
    dump = bson_dumps(bson_loads(dump))
    assert len(dump) == dump_len

    dump = gzip.open('data.mpac.gz', 'rb').read()
    dump_len = len(dump)
    print 'data.mpac:', dump_len, 'bytes'
    dump = msgpack_dumps(msgpack_loads(dump))
    assert len(dump) == dump_len

    print

#=======================================================================================================================
def version():
    print '[version]'
    print 'sys.version:', sys.version
    print 'json.__version__:', json.__version__
    print 'yaml.__version__:', yaml.__version__, 'libyaml=' + str(yaml.__with_libyaml__)
    print 'pymongo.version(bson):', pymongo.version
    print 'ujson.__version__:', ujson.__version__
    print 'msgpack.version:', msgpack.version
    print

#=======================================================================================================================
def main():

    version()
    validationcheck()
    test_load()
    test_dump()

    return 0

#=======================================================================================================================
if __name__ == '__main__':
    sys.exit(main() or 0)
$ python check.py
[version]
sys.version: 2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)]
json.__version__: 2.0.9
yaml.__version__: 3.10 libyaml=True
pymongo.version(bson): 2.3
ujson.__version__: 1.23
msgpack.version: (0, 2, 4)

[data]
data.json: 5999414 bytes
data.json: 5642600 bytes (ujson)
data.yaml: 5876895 bytes
data.yaml: 5876803 bytes (cyaml)
data.bson: 6915449 bytes
data.mpac: 4610724 bytes

[load]
json.loads: 0.10192 sec
yaml.load: 41.04686 sec
yaml.load(libyaml): 5.33758 sec
bson.BSON.decode: 0.04291 sec
ujson.loads: 0.04812 sec
msgpack.loads: 0.01930 sec

[dump]
json.dumps: 0.06862 sec
yaml.dump: 23.78141 sec
yaml.dump(libyaml): 6.38722 sec
bson.BSON.encode: 0.33344 sec
ujson.dumps: 0.04413 sec
msgpack.dumps: 0.03087 sec