ホームページ  >  記事  >  バックエンド開発  >  PyPyとCPythonの性能比較テスト

PyPyとCPythonの性能比較テスト

高洛峰
高洛峰オリジナル
2016-10-18 13:33:281418ブラウズ

最近、Wikipedia でいくつかのデータ マイニング タスクを完了しました。これは次の部分で構成されます:

enwiki-pages-articles.xml の Wikipedia ダンプを解析する;

カテゴリとページを MongoDB に保存する;

カテゴリ名を再分類する。

実際のタスクで CPython 2.7.3 と PyPy 2b のパフォーマンスをテストしました。私が使用したライブラリは次のとおりです:

redis 2.7.2

pymongo 2.4.2

また、CPython は次のライブラリでもサポートされています:

hiredis

pymongo c-extensions

テストは主にデータベースの解析で構成されているため、 PyPy からどれだけのメリットが得られるかは予想していませんでした (CPython のデータベース ドライバーが C で書かれていることは言うまでもありません)。

以下にいくつかの興味深い結果について説明します。


Wikiページ名を抽出します


すべてのWikipediaカテゴリのpage.idへのWikiページ名の結合を作成し、再割り当てされたものを保存する必要があります。最も簡単な解決策は、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->category に対しても同様の結合を行いました (Iラップトップのメモリが小さすぎて、テスト用の情報を保持できません)。


enwiki からカテゴリをフィルターします。したがって、PyPy と CPython の両方で動作するラッパー パーサーである SAX パーサーを選択しました。外部ネイティブ コンパイル パッケージ (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 要素にはタグと本文の情報が含まれており、それをレンダリングするメソッドを提供します。

以下は私が欲しいPyPyとCPythonの比較結果です。

/time

PyPy 2169.90s

CPython 4494.69s

PyPyの結果にはとても驚きました。

興味深いカテゴリのセットの計算

私はかつて、アプリケーションの 1 つのコンテキストで、コンピューティング カテゴリから派生したいくつかのカテゴリから始めて、興味深いカテゴリのセットを計算したいと考えていました。これを行うには、クラスを提供するクラス図、つまりサブクラス図を構築する必要があります。

構造クラスとサブクラスの関係図

このタスクは、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 タスクは CPU の約 10% しか消費しません)。タイミングは次のとおりです:

/time

PyPy 175.11s ユーザーモード 66.11s システムモード 64% CPU

CPython 457.92s ユーザーモード 72.86s システムモード 81% CPU

redis_tree (再配布ツリー) の走査


redis_tree データベースがある場合、残る唯一の問題は、[コンピューティング] カテゴリの下にあるすべての達成可能なノードを走査することです。ループトラバーサルを回避するには、訪問したノードを記録する必要があります。 Python のデータベースのパフォーマンスをテストしたかったので、コレクション列を再配布することでこの問題を解決しました。

/ time

PyPy 14.79s ユーザーモード 6.22s システムモード 69% CPU 30.322 合計

CPython 44.20s ユーザーモード 13.86s システムモード 71% CPU 1:20.91 合計

正直に言うと、このタスクにはいくつかのビルドも必要ですタブー リスト (禁止リスト) - 不要なカテゴリへの入力を避けるため。しかし、それはこの記事の要点ではありません。

結論

実施されたテストは、私の最終的な作業のプレビューにすぎません。それには一連の知識、つまりウィキペディアから適切なコンテンツを抽出して得た一連の知識が必要です。

私の単純なデータベース操作では、CPython と比較して PyPy のパフォーマンスが 2 ~ 3 倍向上しました。 (ここでは SQL パーサーはカウントしていません。約 8 回です)

PyPy のおかげで、私の仕事はより快適になりました - アルゴリズムを書き換えることなく Python を効率的にすることができ、PyPy は CPython のように CPU に負荷をかけませんでした。そのため、しばらくラップトップを通常どおりに使用できなくなりました(CPU 時間の割合を見てください)。

タスクはほぼすべてデータベース操作であり、CPython には高速化された乱雑な C 言語モジュールがいくつかあります。 PyPy はこれらを使用しませんが、結果はより高速です。

私の仕事はすべて多くのサイクルを必要とするので、PyPy を使用することに本当に興奮しています。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。