Jaka jest różnica między tymi dwoma wierszami kodu:
if not x == 'val':
i
if x != 'val':
Czy jedno jest bardziej wydajne od drugiego?
Czy lepiej byłoby użyć
if x == 'val':
pass
else:
Jaka jest różnica między tymi dwoma wierszami kodu:
if not x == 'val':
i
if x != 'val':
Czy jedno jest bardziej wydajne od drugiego?
Czy lepiej byłoby użyć
if x == 'val':
pass
else:
Odpowiedzi:
Używając dis
do przeglądania kodu bajtowego wygenerowanego dla dwóch wersji:
not ==
4 0 LOAD_FAST 0 (foo)
3 LOAD_FAST 1 (bar)
6 COMPARE_OP 2 (==)
9 UNARY_NOT
10 RETURN_VALUE
!=
4 0 LOAD_FAST 0 (foo)
3 LOAD_FAST 1 (bar)
6 COMPARE_OP 3 (!=)
9 RETURN_VALUE
Ten ostatni ma mniej operacji i dlatego może być nieco bardziej wydajny.
W komisjach (dzięki, @Quincunx ) wskazano, że tam, gdzie masz, if foo != bar
a if not foo == bar
liczba operacji jest dokładnie taka sama, po prostu COMPARE_OP
zmiany i zmiana POP_JUMP_IF_TRUE
na POP_JUMP_IF_FALSE
:
not ==
:
2 0 LOAD_FAST 0 (foo)
3 LOAD_FAST 1 (bar)
6 COMPARE_OP 2 (==)
9 POP_JUMP_IF_TRUE 16
!=
2 0 LOAD_FAST 0 (foo)
3 LOAD_FAST 1 (bar)
6 COMPARE_OP 3 (!=)
9 POP_JUMP_IF_FALSE 16
W takim przypadku, chyba że wystąpi różnica w ilości pracy wymaganej do każdego porównania, jest mało prawdopodobne, aby w ogóle dostrzegłaś różnicę w wydajności.
Należy jednak pamiętać, że dwie wersje nie zawsze będą logicznie identyczne , ponieważ będą zależeć od implementacji __eq__
i __ne__
dla przedmiotowych obiektów. Według dokumentacji modelu danych :
Nie ma żadnych domniemanych relacji między operatorami porównania. Prawda
x==y
nie sugeruje, żex!=y
jest to fałsz.
Na przykład:
>>> class Dummy(object):
def __eq__(self, other):
return True
def __ne__(self, other):
return True
>>> not Dummy() == Dummy()
False
>>> Dummy() != Dummy()
True
Wreszcie, a być może najważniejsze: ogólnie rzecz biorąc, gdy oba są logicznie identyczne, x != y
jest o wiele bardziej czytelny niżnot x == y
.
__eq__
niespójna __ne__
jest zepsuta.
not x == y
ma jeszcze jedną instrukcję. Kiedy wstawiłem kod do if
, okazało się, że oba mają tę samą liczbę instrukcji, tylko jedna z nich POP_JUMP_IF_TRUE
i druga POP_JUMP_IF_FALSE
(to była jedyna różnica między nimi, inna niż użycie innej COMPARE_OP
). Kiedy skompilowałem kod bez if
s, dostałem to, co masz.
==
i !=
nie wykluczają się wzajemnie jest SQL-like wdrożeniowych obejmujących null
wartości. W SQL null
nie wraca true
do !=
żadnej innej wartości, więc implementacje interfejsów SQL w Pythonie mogą mieć ten sam problem.
not ==
i !=
wydaje się, że jest to najbardziej interesująca część mojej odpowiedzi! Nie sądzę, że jest to miejsce do zastanowienia się, czy, dlaczego i kiedy ma to sens - patrz np. Dlaczego Python ma __ne__
metodę operatora zamiast tylko __eq__
?
@jonrsharpe ma doskonałe wyjaśnienie tego, co się dzieje. Pomyślałem, że pokażę różnicę czasu podczas uruchamiania każdej z 3 opcji 10 000 000 razy (wystarczy, aby pokazać niewielką różnicę).
Zastosowany kod:
def a(x):
if x != 'val':
pass
def b(x):
if not x == 'val':
pass
def c(x):
if x == 'val':
pass
else:
pass
x = 1
for i in range(10000000):
a(x)
b(x)
c(x)
A wyniki profilera cProfile:
Widzimy więc, że istnieje bardzo niewielka różnica ~ 0,7% między if not x == 'val':
i if x != 'val':
. Spośród nich if x != 'val':
jest najszybszy.
Co jednak najbardziej zaskakujące, widzimy to
if x == 'val':
pass
else:
jest w rzeczywistości najszybszy i bije if x != 'val':
o ~ 0,3%. Nie jest to zbyt czytelne, ale myślę, że jeśli chcesz nieznacznej poprawy wydajności, możesz pójść tą drogą.
W pierwszym Python musi wykonać jeszcze jedną operację więcej niż to konieczne (zamiast po prostu sprawdzić, że nie jest równy, musi sprawdzić, czy nie jest prawdą, że jest równy, a zatem jeszcze jedną operację). Niemożliwe byłoby odróżnienie od jednego wykonania, ale jeśli uruchomione wiele razy, drugie będzie bardziej wydajne. Ogólnie skorzystałbym z drugiego, ale matematycznie są takie same
>>> from dis import dis
>>> dis(compile('not 10 == 20', '', 'exec'))
1 0 LOAD_CONST 0 (10)
3 LOAD_CONST 1 (20)
6 COMPARE_OP 2 (==)
9 UNARY_NOT
10 POP_TOP
11 LOAD_CONST 2 (None)
14 RETURN_VALUE
>>> dis(compile('10 != 20', '', 'exec'))
1 0 LOAD_CONST 0 (10)
3 LOAD_CONST 1 (20)
6 COMPARE_OP 3 (!=)
9 POP_TOP
10 LOAD_CONST 2 (None)
13 RETURN_VALUE
Tutaj możesz zobaczyć, że not x == y
ma jeszcze jedną instrukcję niż x != y
. Różnica w wydajności będzie w większości przypadków bardzo mała, chyba że wykonasz miliony porównań, a nawet wtedy prawdopodobnie nie będzie to przyczyną wąskiego gardła.
Dodatkowa uwaga, ponieważ inne odpowiedzi odpowiedziały na twoje pytanie w większości poprawnie, jest to, że jeśli klasa tylko definiuje, __eq__()
a nie __ne__()
, to COMPARE_OP (!=)
będziesz biegał __eq__()
i negował to. W tym czasie trzecia opcja może być nieco bardziej wydajna, ale należy ją rozważyć tylko wtedy, gdy POTRZEBUJESZ prędkości, ponieważ trudno ją szybko zrozumieć.
Chodzi o twój sposób czytania. not
operator jest dynamiczny, dlatego możesz go zastosować
if not x == 'val':
Ale !=
może być odczytany w lepszym kontekście jako operator, który robi coś przeciwnego do tego, co ==
robi.
not
operator jest dynamiczny” ?
Chcę rozwinąć mój komentarz dotyczący czytelności powyżej.
Ponownie całkowicie zgadzam się z czytelnością przesłaniającą inne problemy (nieznaczące w wydajności).
Chciałbym zwrócić uwagę, że mózg interpretuje „pozytywne” szybciej niż „negatywne”. Np. „Stop” vs. „nie idź” (raczej kiepski przykład z powodu różnicy w liczbie słów).
Tak więc wybór:
if a == b
(do this)
else
(do that)
jest lepszy niż funkcjonalnie równoważny:
if a != b
(do that)
else
(do this)
Mniejsza czytelność / zrozumiałość prowadzi do większej liczby błędów. Być może nie w początkowym kodowaniu, ale zmiany w konserwacji (nie tak mądre jak ty!) ...