-
PyPy 和 CPython 的性能比较测试
最近我在维基百科上完成了一些数据挖掘方面的任务。它由这些部分组成:
解析enwiki-pages-articles.xml的维基百科转储;
把类别和页存储到MongoDB里面;
对类别名称进行重新分门别类。
我对CPython 2.7.3和PyPy 2b的实际任务性能进行了测试。我使用的库是:
redis 2.7.2
pymongo 2.4.2
此外CPython是由以下库支持的:
hiredis
pymongo c-extensions
测试主要包含数据库解析,所以我没预料到会从PyPy得到多大好处(何况CPython的数据库驱动是C写的)。
下面我会描述一些有趣的结果。
抽取维基页名
我需要在所有维基百科的类别中建立维基页名到page.id的联接并存储重新分配好的它们。最简单的解决方案应该是导入enwiki-page.sql(定义了一个RDB表)到MySQL里面,然后传输数据、进行重分配。但我不想增加MySQL需求(有骨气!XD)所以我用纯Python写了一个简单的SQL插入语句解析器,然后直接从enwiki-page.sql导入数据,进行重分配。
这个任务对CPU依赖更大,所以我再次看好PyPy。
/ time
PyPy 169.00s 用户态 8.52s 系统态 90% CPU
CPython 1287.13s 用户态 8.10s 系统态 96% CPU
我也给page.id->类别做了类似的联接(我笔记本的内存太小了,不能保存供我测试的信息了)。
从enwiki.xml中筛选类别
为了方便工作,我需要从enwiki-pages-articles.xml中过滤类别,并将它们存储相同的XML格式的类别。因此我选用了SAX解析器,在PyPy和CPython中都适用的包装器解析。对外的原生编译包(同事在PyPy和CPython 中) 。
代码非常简单:
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
class WikiCategoryHandler(handler.ContentHandler): """Class which detecs category pages and stores them separately """ ignored = set (( 'contributor' , 'comment' , 'meta' )) def __init__( self , f_out): handler.ContentHandler.__init__( self ) self .f_out = f_out self .curr_page = None self .curr_tag = '' self .curr_elem = Element( 'root' , {}) self .root = self .curr_elem self .stack = Stack() self .stack.push( self .curr_elem) self .skip = 0 def startElement( self , name, attrs): if self .skip> 0 or name in self .ignored: self .skip + = 1 return self .curr_tag = name elem = Element(name, attrs) if name = = 'page' : elem.ns = - 1 self .curr_page = elem else : # we don't want to keep old pages in memory self .curr_elem.append(elem) self .stack.push(elem) self .curr_elem = elem def endElement( self , name): if self .skip> 0 : self .skip - = 1 return if name = = 'page' : self .task() self .curr_page = None self .stack.pop() self .curr_elem = self .stack.top() self .curr_tag = self .curr_elem.tag def characters( self , content): if content.isspace(): return if self .skip = = 0 : self .curr_elem.append(TextElement(content)) if self .curr_tag = = 'ns' : self .curr_page.ns = int (content) def startDocument( self ): self .f_out.write( "<root>\n" ) def endDocument( self ): self .f_out.write( "<\root>\n" ) print ( "FINISH PROCESSING WIKIPEDIA" ) def task( self ): if self .curr_page.ns = = 14 : self .f_out.write( self .curr_page.render()) class Element( object ): def __init__( self , tag, attrs): self .tag = tag self .attrs = attrs self .childrens = [] self .append = self .childrens.append def __repr__( self ): return "Element {}" . format ( self .tag) def render( self , margin = 0 ): if not self .childrens: return u "{0}<{1}{2} />" . format ( " " * margin, self .tag, " ".join([' {}=" {}"'. format (k,v) for k,v in {}.iteritems()])) if isinstance ( self .childrens[ 0 ], TextElement) and len ( self .childrens) = = 1 : return u "{0}<{1}{2}>{3}</{1}>" . format ( " " * margin, self .tag, " ".join([u' {}=" {}"'. format (k,v) for k,v in {}.iteritems()]), self .childrens[ 0 ].render()) return u "{0}<{1}{2}>\n{3}\n{0}</{1}>" . format ( " " * margin, self .tag, " ".join([u' {}=" {}"'. format (k,v) for k,v in {}.iteritems()]), "\n" .join((c.render(margin + 2 ) for c in self .childrens))) class TextElement( object ): def __init__( self , content): self .content = content def __repr__( self ): return "TextElement" def render( self , margin = 0 ): return self .content |