web-dev-qa-db-ja.com

奇数をチェックするとき、&は%より速いですか?

奇数と偶数の整数をチェックするには、最下位ビットのチェックはモジュロを使用するよりも効率的ですか?

>>> def isodd(num):
        return num & 1 and True or False

>>> isodd(10)
False
>>> isodd(9)
True
28
riza

うん。標準ライブラリのtimeitモジュールは、これらをチェックする方法です。例えば:

AmAir:stko aleax$ python -mtimeit -s'def isodd(x): x & 1' 'isodd(9)'
1000000 loops, best of 3: 0.446 usec per loop
AmAir:stko aleax$ python -mtimeit -s'def isodd(x): x & 1' 'isodd(10)'
1000000 loops, best of 3: 0.443 usec per loop
AmAir:stko aleax$ python -mtimeit -s'def isodd(x): x % 2' 'isodd(10)'
1000000 loops, best of 3: 0.453 usec per loop
AmAir:stko aleax$ python -mtimeit -s'def isodd(x): x % 2' 'isodd(9)'
1000000 loops, best of 3: 0.461 usec per loop

ご覧のとおり、私の(first-day == old == slow ;-) Macbook Airでは、&ソリューションは%ソリューションよりも7〜18ナノ秒高速です。

timeitは、何が速いかを示すだけでなく、どれだけ(テストを数回実行するだけで)、通常、それがどれほど重要でないかを示します(あなたは本当に関数呼び出しのオーバーヘッドが約400の場合、約10ナノ秒の違いに注意してください?!-).. ..

クヌースから35年(コンピューターが桁違いに速くなった!)であったとしても、マイクロ最適化が本質的に無関係であることをプログラマーに納得させることは不可能な作業であることが証明されています 書いた

効率が小さいこと、たとえば約97%の場合は忘れてください。時期尚早の最適化は、すべての悪の根源です。

彼が説明したように、これはホアからのさらに古い声明からの引用です。私は誰もが彼らのケースが残りの3%に入ると完全に確信していると思います!

したがって、「それは問題ではない」を際限なく繰り返す代わりに、私たち(特にTim Petersはそこで名誉に値する)を標準のPythonライブラリモジュールtimeitに入れ、それを作成しますこのようなマイクロベンチマークを測定するのは簡単で、少なくとも一部のプログラマーは、うーん、この場合は97%のグループに分類されると確信できます!- )

59
Alex Martelli

正直に言うと、私はそれが重要だとは思わない。

最初の問題は読みやすさです。他の開発者にとって何がより理にかなっていますか?個人的には、数値の偶数/奇数をチェックするときにモジュロを期待します。他のほとんどの開発者も同じことを期待していると思います。別の予期しない方法を導入することにより、コードの読み取り、したがって保守がより困難になる可能性があります。

2つ目は、どちらの操作を実行してもボトルネックが発生することはおそらくないという事実です。私は最適化を目指していますが、早期の最適化は、どの言語や環境でも実行できる最悪のことです。何らかの理由で、数値が偶数か奇数かを判断することがボトルネックである場合は、問題を解決するための最速の方法を見つけてください。ただし、これで最初のポイントに戻ります。ルーチンを初めて作成するときは、可能な限り最も読みやすい方法で作成する必要があります。

23
Thomas Owens

あなたが得ることができる最良の最適化はnotテストを関数に入れることです。 'number % 2 'と' number&1 'は奇数/偶数をチェックする非常に一般的な方法であり、経験豊富なプログラマーはパターンを即座に認識し、'#if numberが奇数の場合は、いつでもコメントを挿入できます。あなたは本当にそれが明白である必要があります。

# state whether number is odd or even
if number & 1:
    print "Your number is odd"
else:
    print "Your number is even"
10
too much php

「数値と1を返し、TrueまたはFalseを返す」?ワウ!スピードに夢中の場合(1)「returnnum&1」(2)インライン:if somenumber % 2 == 1は読みやすく、Python関数呼び出しを回避するため、isodd(somenumber)を打ち負かします。

4
John Machin

ジョンは良い点を挙げています。実際のオーバーヘッドは関数呼び出しにあります。

me@localhost ~> python -mtimeit -s'9 % 2'
10000000 loops, best of 3: 0.0271 usec per loop
me@localhost ~> python -mtimeit -s'10 % 2'
10000000 loops, best of 3: 0.0271 usec per loop

me@localhost ~> python -mtimeit -s'9 & 1'
10000000 loops, best of 3: 0.0271 usec per loop
me@localhost ~> python -mtimeit -s'9 & 1'
10000000 loops, best of 3: 0.0271 usec per loop

me@localhost ~> python -mtimeit -s'def isodd(x): x % 2' 'isodd(10)'
1000000 loops, best of 3: 0.334 usec per loop
me@localhost ~> python -mtimeit -s'def isodd(x): x % 2' 'isodd(9)'
1000000 loops, best of 3: 0.358 usec per loop

me@localhost ~> python -mtimeit -s'def isodd(x): x & 1' 'isodd(10)'
1000000 loops, best of 3: 0.317 usec per loop
me@localhost ~> python -mtimeit -s'def isodd(x): x & 1' 'isodd(9)'
1000000 loops, best of 3: 0.319 usec per loop

興味深いことに、両方のメソッドは、関数呼び出しなしで同時にリモアします。

4
Jeffrey Hulten

邪悪な最適化とは別に、すべてのコーダーが2度見なくても理解できる非常に慣用的な「var%2 == 0」を取り除きます。したがって、これはpythons zenにも違反しており、利益はほとんどありません。

さらに、a = bおよびTrueまたはFalseは、読みやすさを向上させるために置き換えられました。

num&1の場合はTrueを返し、それ以外の場合はFalseを返します。

1
Tom

Python 3.6を使用すると、答えはnoです。2017MBPで次のコードを使用すると、モジュロを使用する方が高速であることがわかります。

# odd.py
from datetime import datetime

iterations = 100_000_000


def is_even_modulo(n):
    return not n % 2


def is_even_and(n):
    return not n & 1


def time(fn):
    start = datetime.now()
    for i in range(iterations, iterations * 2):
        fn(i)
    print(f'{fn.__name__}:', datetime.now() - start)


time(is_even_modulo)
time(is_even_and)

この結果が得られます:

$ python3 -m odd
is_even_modulo: 0:00:14.347631
is_even_and: 0:00:17.476522
$ python3 --version
Python 3.6.1

他の回答で示唆されているように、関数呼び出しは大きなオーバーヘッドですが、それを削除すると、モジュロがビット単位よりも高速であり、Python 3.6.1:

# odd.py
from datetime import datetime

iterations = 100_000_000


def time_and():
    start = datetime.now()
    for i in range(iterations):
        i & 1 
    print('&:', datetime.now() - start)


def time_modulo():
    start = datetime.now()
    for i in range(iterations):
        i % 2
    print('%:', datetime.now() - start)


time_modulo()
time_and()

結果:

$ python3 -m odd
%: 0:00:05.134051
&: 0:00:07.250571

ボーナス:これはPython 2.7で実行するのに約2倍の時間がかかることがわかりました。

$ time python2 -m odd
('&:', '0:00:20.169402')
('%:', '0:00:19.837755')

real    0m41.198s
user    0m39.091s
sys 0m1.899s
$ time python3 -m odd
&: 0:00:11.375059
%: 0:00:08.010738

real    0m19.452s
user    0m19.354s
sys 0m0.042s
1
antonagestam

上記の回答のいずれも、変数の設定(タイミングリテラルは別の話です)と関数の呼び出し(明らかに「下位用語」を隠す)の両方を行わなかったことに本当に驚いていました。 ipythonのtimeitからそのタイミングで立ち往生し、明確な勝者x&1を取得しました-python2.6を使用すると約18%(python3.1を使用すると約12%)の方が優れています。

私の非常に古いマシンで:

$ python -mtimeit -s 'x = 777' 'x&1'
10000000 loops, best of 3: 0.18 usec per loop
$ python -mtimeit -s 'x = 777' 'x%2'
1000000 loops, best of 3: 0.219 usec per loop

$ python3 -mtimeit -s 'x = 777' 'x&1'
1000000 loops, best of 3: 0.282 usec per loop
$ python3 -mtimeit -s 'x = 777' 'x%2'
1000000 loops, best of 3: 0.323 usec per loop
0
gorlum0