1. ホーム
  2. python

[解決済み] Pythonで最も高速にアクセスできる構造体的オブジェクトは何ですか?

2023-08-12 01:24:03

質問

構造体のような非常に大きなリストにアクセスすることが主なボトルネックになっているコードを最適化しています。 現在、私は読みやすさのために、名前付きタプルを使用しています。 しかし、'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 テストが無効になってしまうようなことはありませんか? ランダムなシステム イベントによって無効になっていないことを確認するために、それぞれを数回実行しましたが、結果はほとんど同じでした。

辞書はパフォーマンスと読みやすさの間で最も良いバランスを提供し、クラスはその次に来るようです。 私の目的のために、私はオブジェクトがシーケンスのようになることを必要とするので、これは残念なことです。

リストは実質的に高速ですが、定数キーは維持できません。私は、KEY_1 = 1、KEY_2 = 2、などといったインデックス定数の束を作成しなければなりませんが、これも理想的ではありません。

私はこれらの選択肢から抜け出せないのでしょうか、それとも私が見逃している代替案があるのでしょうか。

どのように解決するのですか?

注意点としては、namedtuplesはタプルとしてアクセスするために最適化されていることです。 もし、アクセッサを a[2] の代わりに a.c のようにすれば、タプルと同様のパフォーマンスが得られるでしょう。 その理由は、名前アクセスは事実上 self[idx] の呼び出しに変換されるため、インデックス作成と の両方を支払うことになるからです。

名前によるアクセスはよくあるが、タプルとしてのアクセスはあまりない、というような使用パターンであれば は、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__ は実際には速度的にはあまり役に立ちません。 それはメモリを節約しますが、アクセス時間はそれらなしでほぼ同じです)。

たとえば、リストからサブクラスを作成し、値を属性とリストデータの両方に保存します。 しかし、実際にはリストと同等のパフォーマンスを得ることはできません。 サブクラスを作成しただけで、大きな速度低下が発生します(pure-pythonのオーバーロードのチェックが入る)。 したがって、この場合でも struct[0] には約 0.5 秒かかり(生のリストでは 0.18 秒)、メモリ使用量も 2 倍になるので、これはあまり意味がないかもしれません。