A partir desta página , sabemos que:
As comparações encadeadas são mais rápidas que o uso do
and
operador. Escreva emx < y < z
vez dex < y and y < z
.
No entanto, obtive um resultado diferente testando os seguintes trechos de código:
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.8" "x < y < z"
1000000 loops, best of 3: 0.322 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.8" "x < y and y < z"
1000000 loops, best of 3: 0.22 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.1" "x < y < z"
1000000 loops, best of 3: 0.279 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.1" "x < y and y < z"
1000000 loops, best of 3: 0.215 usec per loop
Parece que x < y and y < z
é mais rápido que x < y < z
. Por quê?
Depois de pesquisar algumas postagens neste site (como esta ), eu sei que "avaliado apenas uma vez" é a chave x < y < z
, mas ainda estou confuso. Para fazer um estudo mais aprofundado, desmontei essas duas funções usando dis.dis
:
import dis
def chained_compare():
x = 1.2
y = 1.3
z = 1.1
x < y < z
def and_compare():
x = 1.2
y = 1.3
z = 1.1
x < y and y < z
dis.dis(chained_compare)
dis.dis(and_compare)
E a saída é:
## chained_compare ##
4 0 LOAD_CONST 1 (1.2)
3 STORE_FAST 0 (x)
5 6 LOAD_CONST 2 (1.3)
9 STORE_FAST 1 (y)
6 12 LOAD_CONST 3 (1.1)
15 STORE_FAST 2 (z)
7 18 LOAD_FAST 0 (x)
21 LOAD_FAST 1 (y)
24 DUP_TOP
25 ROT_THREE
26 COMPARE_OP 0 (<)
29 JUMP_IF_FALSE_OR_POP 41
32 LOAD_FAST 2 (z)
35 COMPARE_OP 0 (<)
38 JUMP_FORWARD 2 (to 43)
>> 41 ROT_TWO
42 POP_TOP
>> 43 POP_TOP
44 LOAD_CONST 0 (None)
47 RETURN_VALUE
## and_compare ##
10 0 LOAD_CONST 1 (1.2)
3 STORE_FAST 0 (x)
11 6 LOAD_CONST 2 (1.3)
9 STORE_FAST 1 (y)
12 12 LOAD_CONST 3 (1.1)
15 STORE_FAST 2 (z)
13 18 LOAD_FAST 0 (x)
21 LOAD_FAST 1 (y)
24 COMPARE_OP 0 (<)
27 JUMP_IF_FALSE_OR_POP 39
30 LOAD_FAST 1 (y)
33 LOAD_FAST 2 (z)
36 COMPARE_OP 0 (<)
>> 39 POP_TOP
40 LOAD_CONST 0 (None)
Parece que o x < y and y < z
comando tem menos disfarces do que x < y < z
. Devo considerar x < y and y < z
mais rápido do que x < y < z
?
Testado com Python 2.7.6 em uma CPU Intel (R) Xeon (E) E5640 a 2.67GHz.
python
performance
zangw
fonte
fonte
timeit
testes, fiquei interessado nisso.y
não é apenas uma pesquisa variável, mas um processo mais caro, como uma chamada de função? Ou seja,10 < max(range(100)) < 15
é mais rápido do que10 < max(range(100)) and max(range(100)) < 15
porquemax(range(100))
é chamado uma vez para as duas comparações.Respostas:
A diferença é que in
x < y < z
y
é avaliado apenas uma vez. Isso não faz grande diferença se y é uma variável, mas ocorre quando é uma chamada de função, que leva algum tempo para ser computada.fonte
sleep()
função interna?O bytecode ideal para as duas funções que você definiu seria
porque o resultado da comparação não é usado. Vamos tornar a situação mais interessante retornando o resultado da comparação. Vamos também fazer com que o resultado não seja conhecido em tempo de compilação.
Novamente, as duas versões da comparação são semanticamente idênticas; portanto, o bytecode ideal é o mesmo para as duas construções. O melhor que posso resolver, seria assim. Anotei cada linha com o conteúdo da pilha antes e depois de cada código de operação, na notação Forth (topo da pilha à direita,
--
divide antes e depois, o trailing?
indica algo que pode ou não estar lá). Observe queRETURN_VALUE
descarta tudo o que é deixado na pilha abaixo do valor retornado.Se uma implementação da linguagem, CPython, PyPy, qualquer que seja, não gera esse bytecode (ou sua própria sequência equivalente de operações) para ambas as variações, isso demonstra a má qualidade desse compilador de bytecode . Obter as seqüências de bytecode postadas acima é um problema resolvido (acho que tudo o que você precisa para este caso é dobragem constante , eliminação de código morto e melhor modelagem do conteúdo da pilha; eliminação comum de subexpressão também seria barata e valiosa ) e realmente não há desculpa para não fazer isso em uma implementação de linguagem moderna.
Agora, acontece que todas as implementações atuais da linguagem possuem compiladores de bytecode de baixa qualidade. Mas você deve ignorar isso enquanto codifica! Finja que o compilador de bytecode é bom e escreva o código mais legível . Provavelmente será bastante rápido de qualquer maneira. Caso contrário, procure primeiro as melhorias algorítmicas e tente o Cython em segundo lugar - isso fornecerá muito mais melhorias para o mesmo esforço do que qualquer ajuste no nível da expressão que você possa aplicar.
fonte
interesting_compare
código de bytes simples na parte superior (que funcionaria apenas com inlining). Completamente offtopic mas: Inlining é uma das otimizações mais essenciais para qualquer compilador. Você pode tentar executar alguns benchmarks com, digamos, HotSpot em programas reais (não em alguns testes de matemática que gastam 99% de seu tempo em um hot loop otimizado manualmente [e, portanto, não possui mais chamadas de funções]) quando você desativa o capacidade de alinhar qualquer coisa - você verá grandes regressões.interesting_compare
.y
não alterar a pilha, pois possui muitas ferramentas de depuração.Como a diferença na saída parece ser devido à falta de otimização, acho que você deve ignorá-la na maioria dos casos - pode ser que a diferença vá embora. A diferença é que
y
só deve ser avaliada uma vez e isso é resolvido duplicando-o na pilha, o que requer um extraPOP_TOP
- a solução a ser usadaLOAD_FAST
ser pode ser possível.A diferença importante, porém, é que no
x<y and y<z
segundoy
deve ser avaliada duas vezes se forx<y
avaliada como verdadeira, isso tem implicações se a avaliação dey
demorar um tempo considerável ou tiver efeitos colaterais.Na maioria dos cenários, você deve usar,
x<y<z
apesar de ser um pouco mais lento.fonte
Primeiro de tudo, sua comparação é praticamente sem sentido, porque as duas construções diferentes não foram introduzidas para fornecer uma melhoria de desempenho; portanto, você não deve decidir se deve usar uma no lugar da outra com base nisso.
A
x < y < z
construção:x
,y
ez
uma vez e verifique se toda a condição segura. O usoand
altera a semântica avaliandoy
várias vezes, o que pode alterar o resultado .Portanto, escolha um no lugar do outro, dependendo da semântica desejada e, se forem equivalentes, se um é mais legível que o outro.
Dito isto: um código mais desmontado não implica um código mais lento. No entanto, executar mais operações de bytecode significa que cada operação é mais simples e, no entanto, requer uma iteração do loop principal. Isso significa que, se as operações que você estiver executando forem extremamente rápidas (por exemplo, pesquisa de variável local como você está fazendo lá), a sobrecarga de execução de mais operações de bytecode poderá ser importante.
Mas observe que esse resultado não se aplica à situação mais genérica, apenas ao "pior caso" que você perfil. Como outros observaram, se você mudar
y
para algo que leva um pouco mais de tempo, verá que os resultados mudam, porque a notação encadeada o avalia apenas uma vez.Resumindo:
fonte