[Python] dict <=> str 변환 시 eval 또는 cPickle Develop Tip

파이썬에서 dict 를 경우에 따라 str 로 변환/역변환 할 필요가 있습니다.
dict 가 nest 되고 커질 수록 자체 dict operation의 cost가 커질 수 밖에 없으므로
궁금해 지게 되었는데요.

암튼 다음과 같은 두 가지 변환을 알고 있습니다.

d = { ... } # 사전 형 정의
sd = str(d) # str로 dict 변환
rd = eval(sd) # eval로 다시 dict 복원

그리고 이런 역할로 Pickle과 cPickle이 있는데
C 모듈로 되어 있어서 빠르다는 cPickle 로

from cPickle import dumps, loads
d = { ... } # 사전 형 정의
sd = dumps(d) # dumps로 dict를 str로 변환
rd = loads(sd) # loads로 다시 dict 복원

그럼 이 둘 간의 속도 차이는 얼마나 날까요??
(페북 친구분 께서 알려주신 python serialisation 의 내용처럼
 marshal과 msgpack 모듈을 이용하도록 추가해 보았습니다)



#!/usr/bin/env python
#coding=utf8

##########################################################################################
from datetime import datetime
import cPickle
import marshal
import msgpack

##########################################################################################
def do_eval(obj, limit):
sts = datetime.now()
for i in xrange(limit):
ds = str(obj)
ls = eval(ds)
if i == 0 and obj != ls: raise RuntimeError('obj revoke error!')
ets = datetime.now()
print('{} eval takes {}...'.format(limit, ets-sts))
##########################################################################################
def do_pickle(obj, limit):
sts = datetime.now()
for i in xrange(limit):
ds = cPickle.dumps(obj)
ls = cPickle.loads(ds)
if i == 0 and obj != ls: raise RuntimeError('obj revoke error!')
ets = datetime.now()
print('{} eval takes {}...'.format(limit, ets-sts))
##########################################################################################
def do_msgpack(obj, limit):
sts = datetime.now()
for i in xrange(limit):
ds = msgpack.packb(obj)
ls = msgpack.unpackb(ds)
if i == 0 and obj != ls: raise RuntimeError('obj revoke error!')
ets = datetime.now()
print('{} eval takes {}...'.format(limit, ets-sts))
##########################################################################################
def do_marshal(obj, limit):
sts = datetime.now()
for i in xrange(limit):
ds = marshal.dumps(obj)
ls = marshal.loads(ds)
if i == 0 and obj != ls: raise RuntimeError('obj revoke error!')
ets = datetime.now()
print('{} eval takes {}...'.format(limit, ets-sts))

##########################################################################################
if __name__ == '__main__':
limit = 100000
obj = {'inpackets': 31125504, 'tunnel_error': 32, 'isregist': False,
'port_line_normal': True, 'cpu_util': 82, 'outpackets': 33349120, 'type': 17,
'isTunnelError': True, 'mem_util': 9, 'sessions': 5, 'activation': True,
'inkbps': 80352, 'corp': 'NNACF', 'name': '\xec\x9e_00162',
'center': 'internet', 'cid': 'f1c6c46e-8fc5-411f-b0b3-8f3061a58e1c',
'net_util': 72, 'droppackets': 894, 'outkbps': 52596, 'time': 1441181768,
'eth': [
{'linkspeed': 0, 'inpackets': 0, 'ip': 0, 'util': 0, 'bandwidth': 0,
'num': 0, 'type': 'normal', 'ipsec': False, 'outbytes': 0,
'isLinkDown': True, 'inbytes': 0, 'dropbytes': 0, 'droppackets': 0, 'kbps': 0},
{'linkspeed': 3703, 'inpackets': 257630, 'ip': 4578, 'util': 75,
'bandwidth': 78828262, 'num': 730, 'type': 'normal', 'ipsec': False,
'outbytes': 5545400, 'isLinkDown': False, 'inbytes': 4139600,
'dropbytes': 703, 'droppackets': 4, 'kbps': 891},
{'linkspeed': 8571, 'inpackets': 143510, 'ip': 3835, 'util': 32,
'bandwidth': 90670011, 'num': 653, 'type': 'normal', 'ipsec': False,
'outbytes': 6588100, 'isLinkDown': False, 'inbytes': 1232100,
'dropbytes': 648, 'droppackets': 8, 'kbps': 4213},
{'linkspeed': 0, 'inpackets': 0, 'ip': 0, 'util': 0, 'bandwidth': 0,
'num': 0, 'type': 'normal', 'ipsec': False, 'outbytes': 0,
'isLinkDown': True, 'inbytes': 0, 'dropbytes': 0, 'droppackets': 0,
'kbps': 0},
{'linkspeed': 29801, 'inpackets': 588020, 'ip': 1952, 'util': 30,
'bandwidth': 83466247, 'num': 581, 'type': 'normal', 'ipsec': False,
'outbytes': 9945500, 'isLinkDown': False, 'inbytes': 3435800,
'dropbytes': 647, 'droppackets': 6, 'kbps': 1092},
{'linkspeed': 0, 'inpackets': 0, 'ip': 0, 'util': 0, 'bandwidth': 0,
'num': 0, 'type': 'normal', 'ipsec': False, 'outbytes': 0,
'isLinkDown': True, 'inbytes': 0, 'dropbytes': 0, 'droppackets': 0,
'kbps': 0},
{'linkspeed': 14359, 'inpackets': 557100, 'ip': 2921, 'util': 32,
'bandwidth': 18209429, 'num': 40, 'type': 'normal', 'ipsec': False,
'outbytes': 1155200, 'isLinkDown': False, 'inbytes': 8787100,
'dropbytes': 906, 'droppackets': 2, 'kbps': 5829},
{'linkspeed': 6704, 'inpackets': 913340, 'ip': 2405, 'util': 73,
'bandwidth': 884716, 'num': 86, 'type': 'normal', 'ipsec': False,
'outbytes': 3145900, 'isLinkDown': False, 'inbytes': 5141200,
'dropbytes': 493, 'droppackets': 0, 'kbps': 11661}], 'dropkbps': 1}
do_eval(obj, limit)
do_pickle(obj, limit)
do_msgpack(obj, limit)
do_marshal(obj, limit)


위와 같은 코드를 돌려 보았습니다.
(timeit 등으로 하지 않아도 뭐 결과는 거의 똑 같았습니다.)

marshal 은 기본 모듈이었기 때문에 그냥 돌리면 되구요,
msgpack은

$ sudo pip install msgpack-python
라고 설치하시면 됩니다.

$ python dict_str.py 
100000 eval takes 0:00:30.173971...
100000 eval takes 0:00:07.915188...
100000 eval takes 0:00:02.108029...
100000 eval takes 0:00:01.497583...


결과에 의하면 cPickle 을 사용하는 경우가 eval을 사용하는 경우보다
거의 4배 정도 더 빠름을 알 수 있었습니다.

헉! marshal 기본 모듈을 이용하면 다시 5배 이상 줄어드는 군요.
원래 eval에 비해서는 20배 이상 차이가 났습니다.

초당 10000개 정도의 무언가를 한다고 할 때,
eval을 사용하면 결코 할 수 없는 상황이 되겠군요...


어느 분께는 도움이 되셨기를...

덧글

  • 꽃선생 2015/09/09 09:04 # 삭제 답글

    언제나 잘배우고 있습니다.
    감사합니다.
    실례가 안된다면 퍼갈 수 있을까요?
  • 지훈현서아빠 2015/09/09 09:19 #

    항상 도움이 되시고 있다니,
    저야 항상 감사드립니다^^
    언제고 퍼 가십시오~
댓글 입력 영역

구글애드텍스트