2019年8月16日

ベンチマーク15 functools.total_ordering

functools.total_ordering を使う場合と使わない場合の動作速度について比較します。
functools.total_ordering を使う場合は、クラスは __eq__() と __lt__() のみ実装し、total_ordering で修飾されています。使わない場合は、クラスは残りの __ne__()、__le__()、__gt__()、__ge__()も実装されています。

==、!=、<、<=、>、>= の6演算子全てを呼んだの速度と、== と < だけを呼んだ場合の速度の、二通りについて比較を行います。


6演算子全てを呼ぶベンチマークのソースコードです。

rom functools import total_ordering
from benchmarker import Benchmarker

with Benchmarker(1000000, width=20, cycle=3, extra=1) as bench:
    @bench("total_ordering")
    def _(bm):
        # クラス定義
        @total_ordering  # デコレータ
        class Stored(object):  # __lt__() と __eq__() のみ定義
            def __init__(self, value):
                self.value = value

            def __lt__(self, rhs):
                return self.value < rhs.value

            def __eq__(self, rhs):
                return self.value == rhs.value

        lhs = Stored(1)
        rhs = Stored(2)
        for _ in bm:
            # 6演算子全てを呼び出し
            lhs == rhs
            lhs != rhs
            lhs < rhs
            lhs <= rhs
            lhs > rhs
            lhs >= rhs

    @bench("normal")
    def _(bm):
        # クラス定義
        class Stored(object):  # 6演算子全てを定義
            def __init__(self, value):
                self.value = value

            def __lt__(self, rhs):
                return self.value < rhs.value

            def __le__(self, rhs):
                return self.value <= rhs.value

            def __gt__(self, rhs):
                return self.value > rhs.value

            def __ge__(self, rhs):
                return self.value >= rhs.value

            def __eq__(self, rhs):
                return self.value == rhs.value

            def __ne__(self, rhs):
                return self.value != rhs.value

        lhs = Stored(1)
        rhs = Stored(2)
        for _ in bm:
            # 6演算子全てを呼び出し
            lhs == rhs
            lhs != rhs
            lhs < rhs
            lhs <= rhs
            lhs > rhs
            lhs >= rhs
測定結果です。
## benchmarker:         release 4.0.1 (for python)
## python version:      3.7.3
## python compiler:     Clang 6.0 (clang-600.0.57)
## python platform:     Darwin-18.7.0-x86_64-i386-64bit
...

## Ranking                real
normal                  1.1379  (100.0) ********************
total_ordering          1.7093  ( 66.6) *************
total_ordering は使わず、6つの演算子を自力で定義した方が、動作速度は速いです。
公式ドキュメントに書いてある通りの結果となりました。




続いて、total_ordering を使っているクラスでも定義されている、== と < だけを呼び出した場合の動作速度を比較します。

rom functools import total_ordering
from benchmarker import Benchmarker

with Benchmarker(1000000, width=20, cycle=3, extra=1) as bench:
    @bench("total_ordering")
    def _(bm):
        # クラス定義
        @total_ordering  # デコレータ
        class Stored(object):  # __lt__() と __eq__() のみ定義
            def __init__(self, value):
                self.value = value

            def __lt__(self, rhs):
                return self.value < rhs.value

            def __eq__(self, rhs):
                return self.value == rhs.value

        lhs = Stored(1)
        rhs = Stored(2)
        for _ in bm:
            # == と < だけ呼び出し
            lhs == rhs
            lhs < rhs

    @bench("normal")
    def _(bm):
        # クラス定義
        class Stored(object):  # 6演算子全てを定義
            def __init__(self, value):
                self.value = value

            def __lt__(self, rhs):
                return self.value < rhs.value

            def __le__(self, rhs):
                return self.value <= rhs.value

            def __gt__(self, rhs):
                return self.value > rhs.value

            def __ge__(self, rhs):
                return self.value >= rhs.value

            def __eq__(self, rhs):
                return self.value == rhs.value

            def __ne__(self, rhs):
                return self.value != rhs.value

        lhs = Stored(1)
        rhs = Stored(2)
        for _ in bm:
            # == と < だけ呼び出し
            lhs == rhs
            lhs < rhs
測定結果です。
## benchmarker:         release 4.0.1 (for python)
## python version:      3.7.3
## python compiler:     Clang 6.0 (clang-600.0.57)
## python platform:     Darwin-18.7.0-x86_64-i386-64bit
...

## Matrix                 real    [01]    [02]
[01] total_ordering     0.4125   100.0   100.3
[02] normal             0.4136    99.7   100.0
結果、動作速度は同じでした。
total_ordering が動作速度的に不利になるのは、未定義の演算子の結果を他の演算子から推測する処理部分にあるようです。

0 件のコメント:

コメントを投稿