PyPy 和 CPython 的性能比较示例

前端之家收集整理的这篇文章主要介绍了PyPy 和 CPython 的性能比较示例前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
对python这个高级语言感兴趣的小伙伴,下面一起跟随编程之家 jb51.cc的小编两巴掌来看看吧!

最近我在维基百科上完成了一些数据挖掘方面的任务。它由这些部分组成:

解析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. # @param PyPy 和 CPython 的性能比较测试
  2. # @author 编程之家 jb51.cc|jb51.cc
  3. class WikiCategoryHandler(handler.ContentHandler):
  4. """Class which detecs category pages and stores them separately
  5. """
  6. ignored = set(('contributor','comment','Meta'))
  7. def __init__(self,f_out):
  8. handler.ContentHandler.__init__(self)
  9. self.f_out = f_out
  10. self.curr_page = None
  11. self.curr_tag = ''
  12. self.curr_elem = Element('root',{})
  13. self.root = self.curr_elem
  14. self.stack = Stack()
  15. self.stack.push(self.curr_elem)
  16. self.skip = 0
  17. def startElement(self,name,attrs):
  18. if self.skip>0 or name in self.ignored:
  19. self.skip += 1
  20. return
  21. self.curr_tag = name
  22. elem = Element(name,attrs)
  23. if name == 'page':
  24. elem.ns = -1
  25. self.curr_page = elem
  26. else: # we don't want to keep old pages in memory
  27. self.curr_elem.append(elem)
  28. self.stack.push(elem)
  29. self.curr_elem = elem
  30. def endElement(self,name):
  31. if self.skip>0:
  32. self.skip -= 1
  33. return
  34. if name == 'page':
  35. self.task()
  36. self.curr_page = None
  37. self.stack.pop()
  38. self.curr_elem = self.stack.top()
  39. self.curr_tag = self.curr_elem.tag
  40. def characters(self,content):
  41. if content.isspace(): return
  42. if self.skip == 0:
  43. self.curr_elem.append(TextElement(content))
  44. if self.curr_tag == 'ns':
  45. self.curr_page.ns = int(content)
  46. def startDocument(self):
  47. self.f_out.write("<root>\n")
  48. def endDocument(self):
  49. self.f_out.write("<\root>\n")
  50. print("FINISH PROCESSING WIKIPEDIA")
  51. def task(self):
  52. if self.curr_page.ns == 14:
  53. self.f_out.write(self.curr_page.render())
  54. class Element(object):
  55. def __init__(self,tag,attrs):
  56. self.tag = tag
  57. self.attrs = attrs
  58. self.childrens = []
  59. self.append = self.childrens.append
  60. def __repr__(self):
  61. return "Element {}".format(self.tag)
  62. def render(self,margin=0):
  63. if not self.childrens:
  64. return u"{0}<{1}{2} />".format(
  65. " "*margin,self.tag,"".join([' {}="{}"'.format(k,v) for k,v in {}.iteritems()]))
  66. if isinstance(self.childrens[0],TextElement) and len(self.childrens)==1:
  67. return u"{0}<{1}{2}>{3}</{1}>".format(
  68. " "*margin,"".join([u' {}="{}"'.format(k,v in {}.iteritems()]),self.childrens[0].render())
  69. return u"{0}<{1}{2}>\n{3}\n{0}</{1}>".format(
  70. " "*margin,"\n".join((c.render(margin+2) for c in self.childrens)))
  71. class TextElement(object):
  72. def __init__(self,content):
  73. self.content = content
  74. def __repr__(self):
  75. return "TextElement" def render(self,margin=0):
  76. return self.content
  77. # End www.jb51.cc

Element和TextElement元素包换tag和body信息,同时提供了一个方法来渲染它。

下面是我想要的PyPy和CPython比较结果。

/ time

PyPy 2169.90s

CPython 4494.69s

我很对PyPy的结果很吃惊。

计算有趣的类别集合

 

我曾经想要计算一个有趣的类别集合——在我的一个应用背景下,以Computing类别衍生的一些类别为开始进行计算。为此我需要构建一个提供类的类图——子类关系图。

结构类——子类关系图

 

这个任务使用MongoDB作为数据来源,并对结构进行重新分配。算法是:

  1. for each category.id in redis_categories (it holds *category.id -> category title mapping*) do:
  2. title = redis_categories.get(category.id)
  3. parent_categories = mongodb get categories for title
  4. for each parent_cat in parent categories do:
  5. redis_tree.sadd(parent_cat,title) # add to parent_cat set title

抱歉写这样的伪码,但我想这样看起来更加紧凑些。

所以说这个任务仅把数据从一个数据库拷贝到另一个。这里的结果是MongoDB预热完毕后得出的(不预热的话数据会有偏差——这个Python任务只耗费约10%的cpu)。计时如下:

/ time

PyPy 175.11s 用户态 66.11s 系统态 64% cpu

CPython 457.92s 用户态 72.86s 系统态 81% cpu

 

遍历redis_tree(再分配过的树)

 

如果我们有redis_tree数据库,仅剩的问题就是遍历Computing类别下所有可实现的结点了。为避免循环遍历,我们需要记录已访问过的结点。自从我想测试Python的数据库性能,我就用再分配集合列来解决这个问题。

/ time

PyPy 14.79s 用户态 6.22s 系统态 69% cpu 30.322 总计

CPython 44.20s 用户态 13.86s 系统态 71% cpu 1:20.91 总计

说实话,这个任务也需要构建一些tabu list(禁止列表)——来避免进入不需要的类别。但那不是本文的重点。

 

结论

进行的测试仅仅是我最终工作的一个简介。它需要一个知识体系,一个我从抽取维基百科中适当的内容中得到的知识体系。

PyPy相比CPython,在我这个简单的数据库操作中,提高了2-3倍的性能。(我这里没有算上sql解析器,大约8倍)

多亏了PyPy,我的工作更加愉悦了——我没有改写算法就使Python有了效率,而且PyPy没有像CPython一样把我的cpu弄挂了,以至于一段时间内我没法正常的使用我的笔记本了(看看cpu时间占的百分比吧)。

任务几乎都是数据库操作,而CPython有一些加速的乱七八糟的C语言模块。PyPy不使用这些,但结果却更快!

我的全部工作需要大量的周期,所以我真高兴能用PyPy。

猜你在找的Python相关文章