首頁  >  文章  >  後端開發  >  PyPy 和 CPython 的效能比較測試

PyPy 和 CPython 的效能比較測試

高洛峰
高洛峰原創
2016-10-18 13:33:281418瀏覽

最近我在維基百科上完成了一些資料探勘的任務。它由這些部分組成:

解析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

id.筆記本的記憶體太小了,不能儲存供我測試的資訊了)。

從enwiki.xml中篩選類別

為了方便工作,我需要從enwiki-pages-articles.xml中過濾類別,並將它們存儲相同的XML格式的類別。因此我選用了SAX解析器,在PyPy和CPython中都適用的包裝器解析。對外的原生編譯套件(同事在PyPy和CPython 中) 。

程式碼非常簡單:

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([&#39; {}="{}"&#39;.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&#39; {}="{}"&#39;.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&#39; {}="{}"&#39;.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

Element和TextElement元素包換tag和body信息,同時提供了一個方法來渲染它。

下面是我想要的PyPy和CPython比較結果。

/ time

PyPy 2169.90s

CPython 4494.69s

我很對PyPy的結果感到驚訝。

我曾經想要計算一個有趣的類別集合

我曾經想要計算一個有趣的類別集合——在我的一個應用背景下,以Computing類別衍生的一些類別為開始進行計算。為此我需要建立一個提供類別的類別圖——子類別關係圖。

結構類別-子類別關係圖

這個任務使用MongoDB作為資料來源,並對結構進行重新分配。演算法是:

for each category.id in redis_categories (it holds *category.id -> category title mapping*) do:
    title = redis_categories.get(category.id)
    parent_categories = mongodb get categories for title
    for each parent_cat in parent categories do:
        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%

如果我們有redis_tree資料庫,僅剩的問題就是遍歷Computing類別下所有可實現的結點了。為避免循環遍歷,我們需要記錄已造訪過的結點。自從我想測試Python的資料庫效能,我就用再分配集合列來解決這個問題。

/ time

PyPy 14.79s 用戶態6.22s 系統態69% CPU 30.322 總計


CPython 44.20s 用戶態13.86s 系統總計 71% CPU list(禁止列表)-來避免進入不需要的類別。但那不是本文的重點。

結論

進行的測試只是我最終工作的一個簡介。它需要一個知識體系,一個我從抽取維基百科中適當的內容中所得到的知識體系。

PyPy相比CPython,在我這個簡單的資料庫操作中,提升了2-3倍的效能。 (我這裡沒有算上SQL解析器,大約8倍)

多虧了PyPy,我的工作更加愉悅了——我沒有改寫算法就使Python有了效率,而且PyPy沒有像CPython一樣把我的CPU弄掛了,以至於一段時間內我沒辦法正常的使用我的筆記本了(看看CPU時間佔的百分比吧)。


任務幾乎都是資料庫操作,而CPython有一些加速的亂七八糟的C語言模組。 PyPy不使用這些,但結果卻更快!

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

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn