web-dev-qa-db-ja.com

Pythonで最も高速な(アクセスする)構造体のようなオブジェクトは何ですか?

主なボトルネックが実行され、構造体に似たオブジェクトの非常に大きなリストにアクセスするコードを最適化しています。現在、読みやすくするためにnamedtuplesを使用しています。しかし、「timeit」を使用したいくつかの簡単なベンチマークは、パフォーマンスが要因である場合、これが本当に間違った方法であることを示しています。

A、b、cを含む名前付きタプル:

>>> timeit("z = a.c", "from __main__ import a")
0.38655471766332994

__slots__を使用するクラス、a、b、c:

>>> timeit("z = b.c", "from __main__ import b")
0.14527461047146062

キーa、b、cの辞書:

>>> timeit("z = c['c']", "from __main__ import c")
0.11588272541098377

定数キーを使用した3つの値を持つタプル:

>>> timeit("z = d[2]", "from __main__ import d")
0.11106188992948773

定数キーを使用して、3つの値でリストします。

>>> timeit("z = e[2]", "from __main__ import e")
0.086038238242508669

ローカルキーを使用した3つの値を持つタプル:

>>> timeit("z = d[key]", "from __main__ import d, key")
0.11187358437882722

ローカルキーを使用して、3つの値をリストします。

>>> timeit("z = e[key]", "from __main__ import e, key")
0.088604143037173344

まず、これらの小さなtimeitテストについて、それらを無効にするものはありますか?ランダムなシステムイベントがそれらをスローしないようにするために、それぞれ数回実行しましたが、結果はほぼ同じでした。

辞書はパフォーマンスと読みやすさの最適なバランスを提供し、クラスは2番目になります。私の目的のために、オブジェクトもシーケンスのようにする必要があるため、これは残念です。したがって、namedtupleの私の選択。

リストはかなり高速ですが、定数キーは維持できません。一連のインデックス定数、つまりKEY_1 = 1、KEY_2 = 2などを作成する必要がありますが、これも理想的ではありません。

私はこれらの選択肢に固執していますか、それとも私が見逃していた選択肢がありますか?

74
DNS

留意すべきことの1つは、namedtuplesがタプルとしてのアクセス用に最適化されていることです。アクセサーをa[2]ではなくa.cに変更すると、タプルと同様のパフォーマンスが得られます。その理由は、名前アクセサがself [idx]の呼び出しに効果的に変換しているため、インデックス作成and名前検索価格の両方を支払うためです。

名前によるアクセスが一般的であるが、Tupleとしてのアクセスはそうではない使用パターンの場合、could反対の方法を行うnamedtupleと同等のクイックを記述します。名前。ただし、その場合はインデックス検索で代金を支払います。例えば、ここに簡単な実装があります:

def makestruct(name, fields):
    fields = fields.split()
    import textwrap
    template = textwrap.dedent("""\
    class {name}(object):
        __slots__ = {fields!r}
        def __init__(self, {args}):
            {self_fields} = {args}
        def __getitem__(self, idx): 
            return getattr(self, fields[idx])
    """).format(
        name=name,
        fields=fields,
        args=','.join(fields), 
        self_fields=','.join('self.' + f for f in fields))
    d = {'fields': fields}
    exec template in d
    return d[name]

ただし、__getitem__を呼び出す必要がある場合、タイミングは非常に悪くなります。

namedtuple.a  :  0.473686933517 
namedtuple[0] :  0.180409193039
struct.a      :  0.180846214294
struct[0]     :  1.32191514969

つまり、属性アクセスの__slots__クラスと同じパフォーマンス(当然のことですが、それがそうです)ですが、インデックスベースのアクセスでの二重検索による大きなペナルティがあります。 (注目に値するのは、__slots__は実際には速度の面ではあまり役に立たないということです。メモリを節約しますが、アクセス時間はそれらなしでもほぼ同じです。)

3番目のオプションは、データを複製することです。リストからサブクラスを作成し、属性とリストデータの両方に値を保存します。ただし、実際にはリストと同等のパフォーマンスは得られません。サブクラス化しただけで大きな速度の打撃があります(pure-pythonのオーバーロードのチェックを行う)。したがって、この場合、struct [0]はまだ0.5秒(未加工リストの0.18と比較して)かかり、メモリ使用量が2倍になるため、これは価値がないかもしれません。

48
Brian

この質問はかなり古い(インターネット時間)ので、今日、通常のCPython(2.7.6)とpypy(2.2.1)の両方でテストを複製して、さまざまな方法がどのように比較されているかを見てみようと思いました。 (名前付きタプルのインデックス付きルックアップにも追加しました。)

これはちょっとしたベンチマークなので、YMMVですが、pypyは名前付きのTupleアクセスをCPythonと比べて30倍高速化しているように見えました(一方、辞書アクセスは3倍しか高速化されていませんでした)。

from collections import namedtuple

STest = namedtuple("TEST", "a b c")
a = STest(a=1,b=2,c=3)

class Test(object):
    __slots__ = ["a","b","c"]

    a=1
    b=2
    c=3

b = Test()

c = {'a':1, 'b':2, 'c':3}

d = (1,2,3)
e = [1,2,3]
f = (1,2,3)
g = [1,2,3]
key = 2

if __== '__main__':
    from timeit import timeit

    print("Named Tuple with a, b, c:")
    print(timeit("z = a.c", "from __main__ import a"))

    print("Named Tuple, using index:")
    print(timeit("z = a[2]", "from __main__ import a"))

    print("Class using __slots__, with a, b, c:")
    print(timeit("z = b.c", "from __main__ import b"))

    print("Dictionary with keys a, b, c:")
    print(timeit("z = c['c']", "from __main__ import c"))

    print("Tuple with three values, using a constant key:")    
    print(timeit("z = d[2]", "from __main__ import d"))

    print("List with three values, using a constant key:")
    print(timeit("z = e[2]", "from __main__ import e"))

    print("Tuple with three values, using a local key:")
    print(timeit("z = d[key]", "from __main__ import d, key"))

    print("List with three values, using a local key:")
    print(timeit("z = e[key]", "from __main__ import e, key"))

Pythonの結果:

Named Tuple with a, b, c:
0.124072679784
Named Tuple, using index:
0.0447055962367
Class using __slots__, with a, b, c:
0.0409136944224
Dictionary with keys a, b, c:
0.0412045334915
Tuple with three values, using a constant key:
0.0449477955531
List with three values, using a constant key:
0.0331083467148
Tuple with three values, using a local key:
0.0453569025139
List with three values, using a local key:
0.033030056702

PyPy結果:

Named Tuple with a, b, c:
0.00444889068604
Named Tuple, using index:
0.00265598297119
Class using __slots__, with a, b, c:
0.00208616256714
Dictionary with keys a, b, c:
0.013897895813
Tuple with three values, using a constant key:
0.00275301933289
List with three values, using a constant key:
0.002760887146
Tuple with three values, using a local key:
0.002769947052
List with three values, using a local key:
0.00278806686401
44
Gerrat

いくつかのポイントとアイデア:

1)連続して同じインデックスに何度もアクセスするタイミングです。実際のプログラムでは、おそらくランダムアクセスまたはリニアアクセスが使用され、動作が異なります。特に、CPUキャッシュミスが多くなります。実際のプログラムを使用すると、若干異なる結果が得られる場合があります。

2)OrderedDictionaryはdictのラッパーとして記述されています。したがって、dictよりも遅くなります。それは非解決策です。

3)新しいスタイルと古いスタイルの両方のクラスを試しましたか? (新しいスタイルのクラスはobjectから継承し、古いスタイルのクラスは継承しません)

4) psyco または nladen Swallow を使用してみましたか?

5)内部ループはデータを変更するのですか、それともアクセスするだけですか?ループに入る前に、データを最も効率的な形式に変換することは可能かもしれませんが、プログラムの他の場所で最も便利な形式を使用してください。

3

私は、(a)何らかのワークロード固有のキャッシュを発明し、パフォーマンスだけでなくスケーラビリティを向上させるために、ストレージとデータの取得をmemcachedbのようなプロセスにオフロードするか、(b)C拡張として書き直したい、ネイティブのデータストレージ。おそらく順序付き辞書タイプ。

これから始めることができます: http://www.xs4all.nl/~anthon/Python/ordereddict/

1
Warren P

この問題は間もなく廃止される可能性があります。 CPython devは明らかに、属性名で名前付きタプル値にアクセスするパフォーマンスを大幅に改善しました。変更は、2019年10月末近くに Python 3.8 でリリースされる予定です。

参照: https://bugs.python.org/issue32492 および https://github.com/python/cpython/pull/10495 .

1
wst